yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <optional>
6#include <stdexcept>
7#include <string>
8#include <string_view>
9#include <unordered_map>
10#include <utility>
11#include <vector>
12
13#include "absl/flags/declare.h"
14#include "absl/flags/flag.h"
15#include "absl/status/statusor.h"
16#include "absl/strings/ascii.h"
17#include "absl/strings/match.h"
18#include "absl/strings/str_cat.h"
19#include "absl/strings/str_format.h"
20#include "absl/strings/str_join.h"
21#include "cli/cli.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
35 const std::vector<std::string>& arg_vec) {
36 int map_id = -1, x = -1, y = -1;
37
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]);
46 }
47 }
48
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>");
52 }
53
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.");
58 }
59
60 Rom rom;
61 auto load_status = rom.LoadFromFile(rom_file);
62 if (!load_status.ok()) {
63 return load_status;
64 }
65 if (!rom.is_loaded()) {
66 return absl::AbortedError("Failed to load ROM.");
67 }
68
69 zelda3::Overworld overworld(&rom);
70 auto ow_status = overworld.Load(&rom);
71 if (!ow_status.ok()) {
72 return ow_status;
73 }
74
75 uint16_t tile = overworld.GetTile(x, y);
76
77 std::cout << "Tile at (" << x << ", " << y << ") on map " << map_id
78 << " is: 0x" << std::hex << tile << std::endl;
79
80 return absl::OkStatus();
81}
82
83// Legacy OverworldSetTile class removed - using new CommandHandler system
84// TODO: Implement OverworldSetTileCommandHandler
86 const std::vector<std::string>& arg_vec) {
87 int map_id = -1, x = -1, y = -1, tile_id = -1;
88
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);
99 }
100 }
101
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 "
105 "<tile_id>");
106 }
107
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.");
112 }
113
114 Rom rom;
115 auto load_status = rom.LoadFromFile(rom_file);
116 if (!load_status.ok()) {
117 return load_status;
118 }
119 if (!rom.is_loaded()) {
120 return absl::AbortedError("Failed to load ROM.");
121 }
122
123 zelda3::Overworld overworld(&rom);
124 auto status = overworld.Load(&rom);
125 if (!status.ok()) {
126 return status;
127 }
128
129 // Set the world based on map_id
130 if (map_id < 0x40) {
131 overworld.set_current_world(0); // Light World
132 } else if (map_id < 0x80) {
133 overworld.set_current_world(1); // Dark World
134 } else {
135 overworld.set_current_world(2); // Special World
136 }
137
138 // Set the tile
139 overworld.SetTile(x, y, static_cast<uint16_t>(tile_id));
140
141 // Save the ROM
142 auto save_status = rom.SaveToFile({.filename = rom_file});
143 if (!save_status.ok()) {
144 return save_status;
145 }
146
147 std::cout << "✅ Set tile at (" << x << ", " << y << ") on map " << map_id
148 << " to: 0x" << std::hex << tile_id << std::dec << std::endl;
149
150 return absl::OkStatus();
151}
152
153namespace {
154
155constexpr absl::string_view kFindTileUsage =
156 "Usage: overworld find-tile --tile <tile_id> [--map <map_id>] [--world "
157 "<light|dark|special|0|1|2>] [--format <json|text>]";
158
159} // namespace
160
161// Legacy OverworldFindTile class removed - using new CommandHandler system
162// TODO: Implement OverworldFindTileCommandHandler
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());
168
169 for (size_t i = 0; i < arg_vec.size(); ++i) {
170 const std::string& token = arg_vec[i];
171 if (absl::StartsWith(token, "--")) {
172 std::string key;
173 std::string value;
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);
178 } else {
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));
183 }
184 value = arg_vec[++i];
185 }
186 if (value.empty()) {
187 return absl::InvalidArgumentError(
188 absl::StrCat("Missing value for --", key, "\n", kFindTileUsage));
189 }
190 options[key] = value;
191 } else {
192 positional.push_back(token);
193 }
194 }
195
196 if (!positional.empty()) {
197 return absl::InvalidArgumentError(absl::StrCat(
198 "Unexpected positional arguments: ", absl::StrJoin(positional, ", "),
199 "\n", kFindTileUsage));
200 }
201
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));
206 }
207
208 ASSIGN_OR_RETURN(int tile_value, overworld::ParseNumeric(tile_it->second));
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, ")"));
213 }
214 const uint16_t tile_id = static_cast<uint16_t>(tile_value);
215
216 std::optional<int> map_filter;
217 if (auto map_it = options.find("map"); map_it != options.end()) {
218 ASSIGN_OR_RETURN(int map_value, overworld::ParseNumeric(map_it->second));
219 if (map_value < 0 || map_value >= 0xA0) {
220 return absl::InvalidArgumentError(
221 absl::StrCat("Map ID out of range: ", map_it->second));
222 }
223 map_filter = map_value;
224 }
225
226 std::optional<int> world_filter;
227 if (auto world_it = options.find("world"); world_it != options.end()) {
228 ASSIGN_OR_RETURN(int parsed_world,
229 overworld::ParseWorldSpecifier(world_it->second));
230 world_filter = parsed_world;
231 }
232
233 if (map_filter.has_value()) {
234 ASSIGN_OR_RETURN(int inferred_world,
235 overworld::InferWorldFromMapId(*map_filter));
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 ",
239 overworld::WorldName(inferred_world), " World but --world requested ",
240 overworld::WorldName(*world_filter)));
241 }
242 if (!world_filter.has_value()) {
243 world_filter = inferred_world;
244 }
245 }
246
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));
253 }
254 }
255
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;
259 }
260
261 if (rom_file.empty()) {
262 return absl::InvalidArgumentError(
263 "ROM file must be provided via --rom flag.");
264 }
265
266 Rom rom;
267 auto load_status = rom.LoadFromFile(rom_file);
268 if (!load_status.ok()) {
269 return load_status;
270 }
271 if (!rom.is_loaded()) {
272 return absl::AbortedError("Failed to load ROM.");
273 }
274
275 zelda3::Overworld overworld(&rom);
276 auto ow_status = overworld.Load(&rom);
277 if (!ow_status.ok()) {
278 return ow_status;
279 }
280
281 overworld::TileSearchOptions search_options;
282 search_options.map_id = map_filter;
283 search_options.world = world_filter;
284
285 ASSIGN_OR_RETURN(auto matches, overworld::FindTileMatches(overworld, tile_id,
286 search_options));
287
288 if (format == "json") {
289 std::cout << "{\n";
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",
299 match.map_id, overworld::WorldName(match.world), match.local_x,
300 match.local_y, match.global_x, match.global_y,
301 (i + 1 == matches.size()) ? "" : ",");
302 }
303 std::cout << " ]\n";
304 std::cout << "}\n";
305 } else {
306 std::cout << absl::StrFormat("🔎 Tile 0x%04X → %zu match(es)\n", tile_id,
307 matches.size());
308 if (matches.empty()) {
309 std::cout << " No matches found." << std::endl;
310 return absl::OkStatus();
311 }
312
313 for (const auto& match : matches) {
314 std::cout << absl::StrFormat(
315 " • Map 0x%02X (%s World) local(%2d,%2d) global(%3d,%3d)\n",
316 match.map_id, overworld::WorldName(match.world), match.local_x,
317 match.local_y, 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(absl::StrCat(
364 "Unexpected positional arguments: ", absl::StrJoin(positional, ", "),
365 "\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, overworld::ParseNumeric(map_it->second));
374 if (map_value < 0 || map_value >= zelda3::kNumOverworldMaps) {
375 return absl::InvalidArgumentError(
376 absl::StrCat("Map ID out of range: ", map_it->second));
377 }
378
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));
385 }
386 }
387
388 std::string rom_file = absl::GetFlag(FLAGS_rom);
389 if (auto it = options.find("rom"); it != options.end()) {
390 rom_file = it->second;
391 }
392
393 if (rom_file.empty()) {
394 return absl::InvalidArgumentError(
395 "ROM file must be provided via --rom flag.");
396 }
397
398 Rom rom;
399 auto load_status = rom.LoadFromFile(rom_file);
400 if (!load_status.ok()) {
401 return load_status;
402 }
403 if (!rom.is_loaded()) {
404 return absl::AbortedError("Failed to load ROM.");
405 }
406
407 zelda3::Overworld overworld_rom(&rom);
408 auto ow_status = overworld_rom.Load(&rom);
409 if (!ow_status.ok()) {
410 return ow_status;
411 }
412
413 ASSIGN_OR_RETURN(auto summary,
414 overworld::BuildMapSummary(overworld_rom, map_value));
415
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));
421 }
422 return absl::StrJoin(parts, ", ");
423 };
424
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));
430 }
431 return absl::StrCat("[", absl::StrJoin(parts, ", "), "]");
432 };
433
434 if (format == "json") {
435 std::cout << "{\n";
436 std::cout << absl::StrFormat(" \"map\": \"0x%02X\",\n", summary.map_id);
437 std::cout << absl::StrFormat(" \"world\": \"%s\",\n",
438 overworld::WorldName(summary.world));
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",
448 summary.message_id);
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);
472 std::cout << "}\n";
473 } else {
474 std::cout << absl::StrFormat("🗺️ Map 0x%02X (%s World)\n", summary.map_id,
475 overworld::WorldName(summary.world));
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 "
488 "(0x%04X)\n",
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));
502 }
503
504 return absl::OkStatus();
505}
506
507// Legacy OverworldListWarps class removed - using new CommandHandler system
508// TODO: Implement OverworldListWarpsCommandHandler
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>]";
515
516 std::unordered_map<std::string, std::string> options;
517 std::vector<std::string> positional;
518 options.reserve(arg_vec.size());
519
520 for (size_t i = 0; i < arg_vec.size(); ++i) {
521 const std::string& token = arg_vec[i];
522 if (absl::StartsWith(token, "--")) {
523 std::string key;
524 std::string value;
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);
529 } else {
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));
534 }
535 value = arg_vec[++i];
536 }
537 if (value.empty()) {
538 return absl::InvalidArgumentError(
539 absl::StrCat("Missing value for --", key, "\n", kUsage));
540 }
541 options[key] = value;
542 } else {
543 positional.push_back(token);
544 }
545 }
546
547 if (!positional.empty()) {
548 return absl::InvalidArgumentError(absl::StrCat(
549 "Unexpected positional arguments: ", absl::StrJoin(positional, ", "),
550 "\n", kUsage));
551 }
552
553 std::optional<int> map_filter;
554 if (auto it = options.find("map"); it != options.end()) {
555 ASSIGN_OR_RETURN(int map_value, overworld::ParseNumeric(it->second));
556 if (map_value < 0 || map_value >= zelda3::kNumOverworldMaps) {
557 return absl::InvalidArgumentError(
558 absl::StrCat("Map ID out of range: ", it->second));
559 }
560 map_filter = map_value;
561 }
562
563 std::optional<int> world_filter;
564 if (auto it = options.find("world"); it != options.end()) {
565 ASSIGN_OR_RETURN(int parsed_world,
567 world_filter = parsed_world;
568 }
569
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") {
574 type_filter = overworld::WarpType::kEntrance;
575 } else if (lower == "hole" || lower == "holes") {
576 type_filter = overworld::WarpType::kHole;
577 } else if (lower == "exit" || lower == "exits") {
578 type_filter = overworld::WarpType::kExit;
579 } else if (lower == "all" || lower.empty()) {
580 type_filter.reset();
581 } else {
582 return absl::InvalidArgumentError(
583 absl::StrCat("Unknown warp type: ", it->second));
584 }
585 }
586
587 if (map_filter.has_value()) {
588 ASSIGN_OR_RETURN(int inferred_world,
589 overworld::InferWorldFromMapId(*map_filter));
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 ",
593 overworld::WorldName(inferred_world), " World but --world requested ",
594 overworld::WorldName(*world_filter)));
595 }
596 if (!world_filter.has_value()) {
597 world_filter = inferred_world;
598 }
599 }
600
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));
607 }
608 }
609
610 std::string rom_file = absl::GetFlag(FLAGS_rom);
611 if (auto it = options.find("rom"); it != options.end()) {
612 rom_file = it->second;
613 }
614
615 if (rom_file.empty()) {
616 return absl::InvalidArgumentError(
617 "ROM file must be provided via --rom flag.");
618 }
619
620 Rom rom;
621 auto load_status = rom.LoadFromFile(rom_file);
622 if (!load_status.ok()) {
623 return load_status;
624 }
625 if (!rom.is_loaded()) {
626 return absl::AbortedError("Failed to load ROM.");
627 }
628
629 zelda3::Overworld overworld_rom(&rom);
630 auto ow_status = overworld_rom.Load(&rom);
631 if (!ow_status.ok()) {
632 return ow_status;
633 }
634
636 query.map_id = map_filter;
637 query.world = world_filter;
638 query.type = type_filter;
639
640 ASSIGN_OR_RETURN(auto entries,
641 overworld::CollectWarpEntries(overworld_rom, query));
642
643 if (format == "json") {
644 std::cout << "{\n";
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];
649 std::cout << " {\n";
650 std::cout << absl::StrFormat(" \"type\": \"%s\",\n",
651 overworld::WarpTypeName(entry.type));
652 std::cout << absl::StrFormat(" \"map\": \"0x%02X\",\n",
653 entry.map_id);
654 std::cout << absl::StrFormat(" \"world\": \"%s\",\n",
655 overworld::WorldName(entry.world));
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,
661 entry.tile16_y);
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",
665 entry.map_pos);
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\"",
672 *entry.entrance_id);
673 }
674 if (entry.entrance_name.has_value()) {
675 std::cout << absl::StrFormat(",\n \"entrance_name\": \"%s\"",
676 *entry.entrance_name);
677 }
678 std::cout << "\n }" << (i + 1 == entries.size() ? "" : ",") << "\n";
679 }
680 std::cout << " ]\n";
681 std::cout << "}\n";
682 } else {
683 if (entries.empty()) {
684 std::cout << "No overworld warps match the specified filters."
685 << std::endl;
686 return absl::OkStatus();
687 }
688
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)",
693 overworld::WarpTypeName(entry.type), entry.map_id,
694 overworld::WorldName(entry.world), entry.tile16_x, entry.tile16_y,
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));
699 }
700 if (entry.entrance_name.has_value()) {
701 line = absl::StrCat(line, " (", *entry.entrance_name, ")");
702 }
703 if (entry.deleted) {
704 line = absl::StrCat(line, " [deleted]");
705 }
706 if (entry.is_hole && entry.type != overworld::WarpType::kHole) {
707 line = absl::StrCat(line, " [hole]");
708 }
709 std::cout << line << std::endl;
710 }
711 }
712
713 return absl::OkStatus();
714}
715
716// ============================================================================
717// Phase 4B: Canvas Automation API Commands
718// ============================================================================
719
720// Legacy OverworldSelectRect class removed - using new CommandHandler system
721// TODO: Implement OverworldSelectRectCommandHandler
723 const std::vector<std::string>& arg_vec) {
724 int map_id = -1, x1 = -1, y1 = -1, x2 = -1, y2 = -1;
725
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]);
738 }
739 }
740
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 "
744 "<x2> --y2 <y2>");
745 }
746
747 std::cout << "✅ Selected rectangle on map " << map_id << " from (" << x1
748 << "," << y1 << ") to (" << x2 << "," << y2 << ")" << std::endl;
749
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;
754
755 return absl::OkStatus();
756}
757
758// Legacy OverworldScrollTo class removed - using new CommandHandler system
759// TODO: Implement OverworldScrollToCommandHandler
761 const std::vector<std::string>& arg_vec) {
762 int map_id = -1, x = -1, y = -1;
763 bool center = false;
764
765 for (size_t i = 0; i < arg_vec.size(); ++i) {
766 const std::string& arg = arg_vec[i];
767 if ((arg == "--map") && i + 1 < arg_vec.size()) {
768 map_id = std::stoi(arg_vec[++i]);
769 } else if ((arg == "--x") && i + 1 < arg_vec.size()) {
770 x = std::stoi(arg_vec[++i]);
771 } else if ((arg == "--y") && i + 1 < arg_vec.size()) {
772 y = std::stoi(arg_vec[++i]);
773 } else if (arg == "--center") {
774 center = true;
775 }
776 }
777
778 if (map_id == -1 || x == -1 || y == -1) {
779 return absl::InvalidArgumentError(
780 "Usage: overworld scroll-to --map <map_id> --x <x> --y <y> [--center]");
781 }
782
783 std::cout << "✅ Scrolled to tile (" << x << "," << y << ") on map "
784 << map_id;
785 if (center) {
786 std::cout << " (centered)";
787 }
788 std::cout << std::endl;
789
790 return absl::OkStatus();
791}
792
793// Legacy OverworldSetZoom class removed - using new CommandHandler system
794// TODO: Implement OverworldSetZoomCommandHandler
796 const std::vector<std::string>& arg_vec) {
797 float zoom = -1.0f;
798
799 for (size_t i = 0; i < arg_vec.size(); ++i) {
800 const std::string& arg = arg_vec[i];
801 if ((arg == "--zoom") && i + 1 < arg_vec.size()) {
802 zoom = std::stof(arg_vec[++i]);
803 }
804 }
805
806 if (zoom < 0.0f) {
807 return absl::InvalidArgumentError(
808 "Usage: overworld set-zoom --zoom <level>\n"
809 " Zoom level: 0.25 - 4.0");
810 }
811
812 // Clamp to valid range
813 zoom = std::max(0.25f, std::min(zoom, 4.0f));
814
815 std::cout << "✅ Set zoom level to " << zoom << "x" << std::endl;
816
817 return absl::OkStatus();
818}
819
820// Legacy OverworldGetVisibleRegion class removed - using new CommandHandler
821// system
822// TODO: Implement OverworldGetVisibleRegionCommandHandler
824 const std::vector<std::string>& arg_vec) {
825 int map_id = -1;
826 std::string format = "text";
827
828 for (size_t i = 0; i < arg_vec.size(); ++i) {
829 const std::string& arg = arg_vec[i];
830 if ((arg == "--map") && i + 1 < arg_vec.size()) {
831 map_id = std::stoi(arg_vec[++i]);
832 } else if ((arg == "--format") && i + 1 < arg_vec.size()) {
833 format = arg_vec[++i];
834 }
835 }
836
837 if (map_id == -1) {
838 return absl::InvalidArgumentError(
839 "Usage: overworld get-visible-region --map <map_id> [--format "
840 "json|text]");
841 }
842
843 // Note: This would query the canvas automation API in a live GUI context
844 // For now, return placeholder data
845 if (format == "json") {
846 std::cout << R"({
847 "map_id": )" << map_id
848 << R"(,
849 "visible_region": {
850 "min_x": 0,
851 "min_y": 0,
852 "max_x": 31,
853 "max_y": 31
854 },
855 "tile_count": 1024
856})" << std::endl;
857 } else {
858 std::cout << "Visible region on map " << map_id << ":" << std::endl;
859 std::cout << " Min: (0, 0)" << std::endl;
860 std::cout << " Max: (31, 31)" << std::endl;
861 std::cout << " Total visible tiles: 1024" << std::endl;
862 }
863
864 return absl::OkStatus();
865}
866
867} // namespace cli
868} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:74
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:164
bool is_loaded() const
Definition rom.h:128
Represents the full Overworld data, light and dark world.
Definition overworld.h:217
void set_current_world(int world)
Definition overworld.h:535
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:36
uint16_t GetTile(int x, int y) const
Definition overworld.h:536
void SetTile(int x, int y, uint16_t tile_id)
Definition overworld.h:545
ABSL_DECLARE_FLAG(std::string, rom)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
constexpr absl::string_view kFindTileUsage
Definition overworld.cc:155
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:163
absl::Status HandleOverworldListWarpsLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:509
absl::Status HandleOverworldSetZoomLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:795
absl::Status HandleOverworldSelectRectLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:722
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:85
absl::Status HandleOverworldScrollToLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:760
absl::Status HandleOverworldGetVisibleRegionLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:823
constexpr int kNumOverworldMaps
Definition common.h:85
std::optional< WarpType > type