yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_graph_commands.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdint>
5#include <queue>
6#include <set>
7#include <string>
8#include <vector>
9
10#include "absl/strings/numbers.h"
11#include "absl/strings/str_format.h"
12#include "cli/util/hex_util.h"
13#include "rom/rom.h"
14#include "zelda3/dungeon/room.h"
16
17namespace yaze {
18namespace cli {
19namespace handlers {
20
22
23namespace {
24
25// Edge types for room connections
26constexpr const char* kEdgeTypeStair1 = "stair1";
27constexpr const char* kEdgeTypeStair2 = "stair2";
28constexpr const char* kEdgeTypeStair3 = "stair3";
29constexpr const char* kEdgeTypeStair4 = "stair4";
30constexpr const char* kEdgeTypeHolewarp = "holewarp";
31
32struct RoomNode {
34 std::string name;
35 uint8_t staircase_rooms[4];
36 uint8_t holewarp;
38};
39
40struct RoomEdge {
43 std::string type;
44};
45
46// Get the dungeon ID for a room by checking entrances
47int GetRoomDungeonId(Rom* rom, int room_id) {
48 // Scan entrances to find one that leads to this room
49 // This is an approximation - some rooms may not have direct entrances
50 for (int i = 0; i < 0x84; ++i) {
51 zelda3::RoomEntrance entrance(rom, static_cast<uint8_t>(i), false);
52 if (entrance.room_ == room_id) {
53 return entrance.dungeon_id_;
54 }
55 }
56 return -1; // Unknown dungeon
57}
58
59// Returns true for door types that exit the dungeon (overworld, cave exit, etc.)
76
77// Compute neighbor room ID from door direction using ALTTP 16-wide grid.
78// Returns -1 if the computed ID is out of range.
80 int neighbor = -1;
81 switch (dir) {
83 neighbor = room_id - 0x10;
84 break;
86 neighbor = room_id + 0x10;
87 break;
89 neighbor = room_id - 0x01;
90 break;
92 neighbor = room_id + 0x01;
93 break;
94 default:
95 break;
96 }
97 if (neighbor < 0 || neighbor >= zelda3::kNumberOfRooms)
98 return -1;
99 return neighbor;
100}
101
102// Opposite direction for reciprocal door check
117
118// Check if a room has a non-exit door in the given direction.
119// Used to verify reciprocal connectivity (A→North→B requires B has a South door).
120bool RoomHasDoorIn(Rom* rom, int room_id, zelda3::DoorDirection dir) {
121 zelda3::Room neighbor_room = zelda3::LoadRoomFromRom(rom, room_id);
122 for (const auto& door : neighbor_room.GetDoors()) {
123 if (door.direction == dir && !IsExitDoorType(door.type))
124 return true;
125 }
126 return false;
127}
128
130 switch (dir) {
132 return "door_north";
134 return "door_south";
136 return "door_west";
138 return "door_east";
139 default:
140 return "door_unknown";
141 }
142}
143
144} // namespace
145
147 Rom* rom, const resources::ArgumentParser& parser,
148 resources::OutputFormatter& formatter) {
149 // Parse optional filters
150 auto room_id_opt = parser.GetString("room");
151 auto dungeon_id_opt = parser.GetString("dungeon");
152
153 int room_filter = -1;
154 int dungeon_filter = -1;
155
156 if (room_id_opt.has_value()) {
157 if (!ParseHexString(room_id_opt.value(), &room_filter)) {
158 return absl::InvalidArgumentError(
159 "Invalid room ID format. Must be hex (e.g., 0x07).");
160 }
161 }
162
163 if (dungeon_id_opt.has_value()) {
164 if (!ParseHexString(dungeon_id_opt.value(), &dungeon_filter)) {
165 return absl::InvalidArgumentError(
166 "Invalid dungeon ID format. Must be hex (e.g., 0x02).");
167 }
168 }
169
170 // Build the graph
171 std::vector<RoomNode> nodes;
172 std::vector<RoomEdge> edges;
173 std::set<int> rooms_with_edges;
174
175 // Determine scan range
176 int start_room = (room_filter >= 0) ? room_filter : 0;
177 int end_room = (room_filter >= 0) ? room_filter : zelda3::kNumberOfRooms - 1;
178
179 for (int room_id = start_room; room_id <= end_room; ++room_id) {
180 // Load room header to get staircase and holewarp data
181 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
182
183 // Skip if filtering by dungeon
184 if (dungeon_filter >= 0) {
185 int room_dungeon = GetRoomDungeonId(rom, room_id);
186 if (room_dungeon != dungeon_filter) {
187 continue;
188 }
189 }
190
191 RoomNode node;
192 node.room_id = room_id;
193 // Bounds check for kRoomNames (array size is 297)
194 if (room_id >= 0 && room_id < 297) {
195 node.name = std::string(zelda3::kRoomNames[room_id]);
196 } else {
197 node.name = absl::StrFormat("Room 0x%02X", room_id);
198 }
199 node.holewarp = room.holewarp();
200 node.has_connections = false;
201
202 // Extract staircase destinations
203 for (int i = 0; i < 4; ++i) {
204 node.staircase_rooms[i] = room.staircase_room(i);
205
206 // Create edge if destination is valid (non-zero)
207 if (node.staircase_rooms[i] != 0) {
208 RoomEdge edge;
209 edge.from_room = room_id;
210 edge.to_room = node.staircase_rooms[i];
211
212 switch (i) {
213 case 0:
214 edge.type = kEdgeTypeStair1;
215 break;
216 case 1:
217 edge.type = kEdgeTypeStair2;
218 break;
219 case 2:
220 edge.type = kEdgeTypeStair3;
221 break;
222 case 3:
223 edge.type = kEdgeTypeStair4;
224 break;
225 }
226
227 edges.push_back(edge);
228 node.has_connections = true;
229 rooms_with_edges.insert(room_id);
230 rooms_with_edges.insert(node.staircase_rooms[i]);
231 }
232 }
233
234 // Create holewarp edge if valid
235 if (node.holewarp != 0) {
236 RoomEdge edge;
237 edge.from_room = room_id;
238 edge.to_room = node.holewarp;
239 edge.type = kEdgeTypeHolewarp;
240 edges.push_back(edge);
241 node.has_connections = true;
242 rooms_with_edges.insert(room_id);
243 rooms_with_edges.insert(node.holewarp);
244 }
245
246 nodes.push_back(node);
247 }
248
249 // Output the graph
250 formatter.BeginObject("dungeon_graph");
251
252 // Nodes array - only include nodes with connections for cleaner output
253 formatter.BeginArray("nodes");
254 for (const auto& node : nodes) {
255 // Include all nodes if filtering by room, otherwise only connected ones
256 if (room_filter >= 0 || node.has_connections ||
257 rooms_with_edges.count(node.room_id)) {
258 formatter.BeginObject();
259 formatter.AddField("room_id", absl::StrFormat("0x%02X", node.room_id));
260 formatter.AddField("name", node.name);
261
262 // Staircase array
263 formatter.BeginArray("stairs");
264 for (int i = 0; i < 4; ++i) {
265 formatter.AddArrayItem(
266 absl::StrFormat("0x%02X", node.staircase_rooms[i]));
267 }
268 formatter.EndArray();
269
270 formatter.AddField("holewarp", absl::StrFormat("0x%02X", node.holewarp));
271 formatter.EndObject();
272 }
273 }
274 formatter.EndArray();
275
276 // Edges array
277 formatter.BeginArray("edges");
278 for (const auto& edge : edges) {
279 formatter.BeginObject();
280 formatter.AddField("from", absl::StrFormat("0x%02X", edge.from_room));
281 formatter.AddField("to", absl::StrFormat("0x%02X", edge.to_room));
282 formatter.AddField("type", edge.type);
283 formatter.EndObject();
284 }
285 formatter.EndArray();
286
287 // Statistics
288 formatter.BeginObject("stats");
289 formatter.AddField("total_rooms_scanned",
290 static_cast<int>(end_room - start_room + 1));
291 formatter.AddField("total_nodes", static_cast<int>(rooms_with_edges.size()));
292 formatter.AddField("total_edges", static_cast<int>(edges.size()));
293
294 // Count edge types
295 int stair_edges = 0;
296 int hole_edges = 0;
297 for (const auto& edge : edges) {
298 if (edge.type == kEdgeTypeHolewarp) {
299 hole_edges++;
300 } else {
301 stair_edges++;
302 }
303 }
304 formatter.AddField("staircase_connections", stair_edges);
305 formatter.AddField("holewarp_connections", hole_edges);
306 formatter.EndObject();
307
308 formatter.EndObject();
309
310 return absl::OkStatus();
311}
312
314 Rom* rom, const resources::ArgumentParser& parser,
315 resources::OutputFormatter& formatter) {
316 auto entrance_id_str = parser.GetString("entrance").value();
317 bool is_spawn_point = parser.HasFlag("spawn");
318
319 int entrance_id;
320 if (!ParseHexString(entrance_id_str, &entrance_id)) {
321 return absl::InvalidArgumentError(
322 "Invalid entrance ID format. Must be hex (e.g., 0x08).");
323 }
324
325 // Validate entrance ID range
326 if (entrance_id < 0 || entrance_id > 0x84) {
327 return absl::InvalidArgumentError(absl::StrFormat(
328 "Entrance ID 0x%02X out of range (0x00-0x84).", entrance_id));
329 }
330
331 zelda3::RoomEntrance entrance(rom, static_cast<uint8_t>(entrance_id),
332 is_spawn_point);
333
334 formatter.BeginObject("entrance");
335 formatter.AddField("entrance_id", absl::StrFormat("0x%02X", entrance_id));
336 formatter.AddField("is_spawn_point", is_spawn_point);
337 formatter.AddField("room_id",
338 absl::StrFormat("0x%02X", entrance.room_ & 0xFF));
339 formatter.AddField("room_id_full", absl::StrFormat("0x%04X", entrance.room_));
340 formatter.AddField("dungeon_id",
341 absl::StrFormat("0x%02X", entrance.dungeon_id_));
342 formatter.AddField("exit_id", absl::StrFormat("0x%04X", entrance.exit_));
343
344 formatter.BeginObject("position");
345 formatter.AddField("x", entrance.x_position_);
346 formatter.AddField("y", entrance.y_position_);
347 formatter.EndObject();
348
349 formatter.BeginObject("camera");
350 formatter.AddField("x", entrance.camera_x_);
351 formatter.AddField("y", entrance.camera_y_);
352 formatter.AddField("trigger_x", entrance.camera_trigger_x_);
353 formatter.AddField("trigger_y", entrance.camera_trigger_y_);
354 formatter.EndObject();
355
356 formatter.BeginObject("properties");
357 formatter.AddField("blockset", absl::StrFormat("0x%02X", entrance.blockset_));
358 formatter.AddField("floor", absl::StrFormat("0x%02X", entrance.floor_));
359 formatter.AddField("door", absl::StrFormat("0x%02X", entrance.door_));
360 formatter.AddField("ladder_bg",
361 absl::StrFormat("0x%02X", entrance.ladder_bg_));
362 formatter.AddField("scrolling",
363 absl::StrFormat("0x%02X", entrance.scrolling_));
364 formatter.AddField("scroll_quadrant",
365 absl::StrFormat("0x%02X", entrance.scroll_quadrant_));
366 formatter.AddField("music", absl::StrFormat("0x%02X", entrance.music_));
367 formatter.EndObject();
368
369 formatter.BeginObject("camera_boundaries");
370 formatter.AddField("qn",
371 absl::StrFormat("0x%02X", entrance.camera_boundary_qn_));
372 formatter.AddField("fn",
373 absl::StrFormat("0x%02X", entrance.camera_boundary_fn_));
374 formatter.AddField("qs",
375 absl::StrFormat("0x%02X", entrance.camera_boundary_qs_));
376 formatter.AddField("fs",
377 absl::StrFormat("0x%02X", entrance.camera_boundary_fs_));
378 formatter.AddField("qw",
379 absl::StrFormat("0x%02X", entrance.camera_boundary_qw_));
380 formatter.AddField("fw",
381 absl::StrFormat("0x%02X", entrance.camera_boundary_fw_));
382 formatter.AddField("qe",
383 absl::StrFormat("0x%02X", entrance.camera_boundary_qe_));
384 formatter.AddField("fe",
385 absl::StrFormat("0x%02X", entrance.camera_boundary_fe_));
386 formatter.EndObject();
387
388 formatter.EndObject();
389
390 return absl::OkStatus();
391}
392
394 Rom* rom, const resources::ArgumentParser& parser,
395 resources::OutputFormatter& formatter) {
396 auto entrance_id_str = parser.GetString("entrance").value();
397 auto depth_opt = parser.GetString("depth");
398
399 int entrance_id;
400 if (!ParseHexString(entrance_id_str, &entrance_id)) {
401 return absl::InvalidArgumentError(
402 "Invalid entrance ID format. Must be hex (e.g., 0x08).");
403 }
404
405 // Validate entrance ID range
406 if (entrance_id < 0 || entrance_id > 0x84) {
407 return absl::InvalidArgumentError(absl::StrFormat(
408 "Entrance ID 0x%02X out of range (0x00-0x84).", entrance_id));
409 }
410
411 int max_depth = 20; // Default depth limit
412 if (depth_opt.has_value()) {
413 if (!absl::SimpleAtoi(depth_opt.value(), &max_depth)) {
414 return absl::InvalidArgumentError(
415 "Invalid depth format. Must be an integer between 1 and 100.");
416 }
417 if (max_depth < 1 || max_depth > 100) {
418 return absl::InvalidArgumentError("Depth must be between 1 and 100.");
419 }
420 }
421
422 // Get starting room from entrance
423 zelda3::RoomEntrance entrance(rom, static_cast<uint8_t>(entrance_id), false);
424 int start_room = entrance.room_ & 0xFF;
425
426 // BFS to discover all connected rooms
427 std::set<int> discovered_rooms;
428 std::vector<RoomEdge> edges;
429 std::queue<std::pair<int, int>> to_visit; // (room_id, depth)
430
431 to_visit.push({start_room, 0});
432 discovered_rooms.insert(start_room);
433
434 while (!to_visit.empty()) {
435 auto [current_room, current_depth] = to_visit.front();
436 to_visit.pop();
437
438 if (current_depth >= max_depth) {
439 continue;
440 }
441
442 // Load room to get connections
443 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, current_room);
444
445 // Check staircase connections
446 for (int i = 0; i < 4; ++i) {
447 uint8_t dest = room.staircase_room(i);
448 if (dest != 0 && discovered_rooms.find(dest) == discovered_rooms.end()) {
449 discovered_rooms.insert(dest);
450 to_visit.push({dest, current_depth + 1});
451 }
452 if (dest != 0) {
453 RoomEdge edge;
454 edge.from_room = current_room;
455 edge.to_room = dest;
456 edge.type = absl::StrFormat("stair%d", i + 1);
457 edges.push_back(edge);
458 }
459 }
460
461 // Check holewarp connection
462 if (room.holewarp() != 0 &&
463 discovered_rooms.find(room.holewarp()) == discovered_rooms.end()) {
464 discovered_rooms.insert(room.holewarp());
465 to_visit.push({room.holewarp(), current_depth + 1});
466 }
467 if (room.holewarp() != 0) {
468 RoomEdge edge;
469 edge.from_room = current_room;
470 edge.to_room = room.holewarp();
471 edge.type = "holewarp";
472 edges.push_back(edge);
473 }
474 }
475
476 // Output results
477 formatter.BeginObject("discovery");
478 formatter.AddField("entrance_id", absl::StrFormat("0x%02X", entrance_id));
479 formatter.AddField("start_room", absl::StrFormat("0x%02X", start_room));
480 formatter.AddField("dungeon_id",
481 absl::StrFormat("0x%02X", entrance.dungeon_id_));
482 formatter.AddField("max_depth", max_depth);
483 formatter.AddField("rooms_discovered",
484 static_cast<int>(discovered_rooms.size()));
485
486 // Room list
487 formatter.BeginArray("discovered_rooms");
488 std::vector<int> sorted_rooms(discovered_rooms.begin(),
489 discovered_rooms.end());
490 std::sort(sorted_rooms.begin(), sorted_rooms.end());
491 for (int room_id : sorted_rooms) {
492 formatter.BeginObject();
493 formatter.AddField("room_id", absl::StrFormat("0x%02X", room_id));
494 // Bounds check for kRoomNames
495 if (room_id >= 0 && room_id < 297) {
496 formatter.AddField("name", std::string(zelda3::kRoomNames[room_id]));
497 } else {
498 formatter.AddField("name", absl::StrFormat("Room 0x%02X", room_id));
499 }
500 formatter.EndObject();
501 }
502 formatter.EndArray();
503
504 // Connection graph
505 formatter.BeginArray("connections");
506 for (const auto& edge : edges) {
507 formatter.BeginObject();
508 formatter.AddField("from", absl::StrFormat("0x%02X", edge.from_room));
509 formatter.AddField("to", absl::StrFormat("0x%02X", edge.to_room));
510 formatter.AddField("type", edge.type);
511 formatter.EndObject();
512 }
513 formatter.EndArray();
514
515 formatter.EndObject();
516
517 return absl::OkStatus();
518}
519
521 Rom* rom, const resources::ArgumentParser& parser,
522 resources::OutputFormatter& formatter) {
523 auto entrance_id_str = parser.GetString("entrance").value();
524 auto depth_opt = parser.GetString("depth");
525
526 int entrance_id;
527 if (!ParseHexString(entrance_id_str, &entrance_id)) {
528 return absl::InvalidArgumentError(
529 "Invalid entrance ID format. Must be hex (e.g., 0x27).");
530 }
531 if (entrance_id < 0 || entrance_id > 0x84) {
532 return absl::InvalidArgumentError(absl::StrFormat(
533 "Entrance ID 0x%02X out of range (0x00-0x84).", entrance_id));
534 }
535
536 int max_depth = 50;
537 if (depth_opt.has_value()) {
538 if (!absl::SimpleAtoi(depth_opt.value(), &max_depth)) {
539 return absl::InvalidArgumentError(
540 "Invalid depth format. Must be an integer between 1 and 200.");
541 }
542 if (max_depth < 1 || max_depth > 200) {
543 return absl::InvalidArgumentError("Depth must be between 1 and 200.");
544 }
545 }
546
547 bool same_blockset_filter = parser.HasFlag("same-blockset");
548
549 zelda3::RoomEntrance entrance(rom, static_cast<uint8_t>(entrance_id), false);
550 int start_room = entrance.room_ & 0xFF;
551
552 // Get starting room's blockset for optional filtering
553 uint8_t start_blockset = 0xFF;
554 if (same_blockset_filter) {
555 zelda3::Room start_room_data =
556 zelda3::LoadRoomHeaderFromRom(rom, start_room);
557 start_blockset = start_room_data.blockset();
558 }
559
560 struct DoorEdge {
561 int from_room;
562 int to_room; // -1 if exit
563 std::string type; // "door_north", "door_south", etc.
564 std::string door_type_name;
565 int tile_x;
566 int tile_y;
567 bool is_exit;
568 };
569
570 struct StairEdge {
571 int from_room;
572 int to_room;
573 std::string type; // "stair1"-"stair4" or "holewarp"
574 };
575
576 std::set<int> visited;
577 std::vector<DoorEdge> door_edges;
578 std::vector<StairEdge> stair_edges;
579 std::queue<std::pair<int, int>> to_visit; // (room_id, depth)
580
581 to_visit.push({start_room, 0});
582 visited.insert(start_room);
583
584 while (!to_visit.empty()) {
585 auto [room_id, depth] = to_visit.front();
586 to_visit.pop();
587
588 if (depth >= max_depth)
589 continue;
590
591 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
592
593 // Door edges — infer neighbor from grid position + direction
594 for (const auto& door : room.GetDoors()) {
595 bool is_exit = IsExitDoorType(door.type);
596 int neighbor = is_exit ? -1 : NeighborRoomId(room_id, door.direction);
597 auto [tx, ty] = door.GetTileCoords();
598
599 DoorEdge edge;
600 edge.from_room = room_id;
601 edge.to_room = neighbor;
602 edge.type = DoorEdgeTypeName(door.direction);
603 edge.door_type_name = std::string(door.GetTypeName());
604 edge.tile_x = tx;
605 edge.tile_y = ty;
606 edge.is_exit = is_exit;
607 door_edges.push_back(edge);
608
609 // Only follow non-exit doors that have a reciprocal door on the other side.
610 // This prevents cascading across the entire dungeon grid.
611 if (!is_exit && neighbor >= 0 &&
612 visited.find(neighbor) == visited.end() &&
613 RoomHasDoorIn(rom, neighbor, OppositeDir(door.direction))) {
614 // Optional: skip neighbors with a different blockset than the start room
615 if (same_blockset_filter) {
616 zelda3::Room nbr = zelda3::LoadRoomHeaderFromRom(rom, neighbor);
617 if (nbr.blockset() != start_blockset)
618 continue;
619 }
620 visited.insert(neighbor);
621 to_visit.push({neighbor, depth + 1});
622 }
623 }
624
625 // Staircase edges
626 for (int i = 0; i < 4; ++i) {
627 uint8_t dest = room.staircase_room(i);
628 if (dest == 0)
629 continue;
630 StairEdge edge;
631 edge.from_room = room_id;
632 edge.to_room = dest;
633 edge.type = absl::StrFormat("stair%d", i + 1);
634 stair_edges.push_back(edge);
635 if (visited.find(dest) == visited.end()) {
636 visited.insert(dest);
637 to_visit.push({dest, depth + 1});
638 }
639 }
640
641 // Holewarp edge
642 uint8_t hw = room.holewarp();
643 if (hw != 0) {
644 StairEdge edge;
645 edge.from_room = room_id;
646 edge.to_room = hw;
647 edge.type = "holewarp";
648 stair_edges.push_back(edge);
649 if (visited.find(hw) == visited.end()) {
650 visited.insert(hw);
651 to_visit.push({hw, depth + 1});
652 }
653 }
654 }
655
656 // Output
657 formatter.BeginObject("room_graph");
658 formatter.AddField("entrance_id", absl::StrFormat("0x%02X", entrance_id));
659 formatter.AddField("start_room", absl::StrFormat("0x%02X", start_room));
660 formatter.AddField("dungeon_id",
661 absl::StrFormat("0x%02X", entrance.dungeon_id_));
662 formatter.AddField("rooms_discovered", static_cast<int>(visited.size()));
663
664 formatter.BeginArray("rooms");
665 std::vector<int> sorted_rooms(visited.begin(), visited.end());
666 std::sort(sorted_rooms.begin(), sorted_rooms.end());
667 for (int rid : sorted_rooms) {
668 formatter.BeginObject();
669 formatter.AddField("room_id", absl::StrFormat("0x%02X", rid));
670 if (rid >= 0 && rid < 297) {
671 formatter.AddField("name", std::string(zelda3::kRoomNames[rid]));
672 } else {
673 formatter.AddField("name", absl::StrFormat("Room 0x%02X", rid));
674 }
675 formatter.EndObject();
676 }
677 formatter.EndArray();
678
679 // Door edges (include exits so the navigator knows where NOT to go)
680 formatter.BeginArray("door_edges");
681 for (const auto& edge : door_edges) {
682 formatter.BeginObject();
683 formatter.AddField("from", absl::StrFormat("0x%02X", edge.from_room));
684 if (edge.is_exit || edge.to_room < 0) {
685 formatter.AddField("to", "exit");
686 } else {
687 formatter.AddField("to", absl::StrFormat("0x%02X", edge.to_room));
688 }
689 formatter.AddField("type", edge.type);
690 formatter.AddField("door_type", edge.door_type_name);
691 formatter.AddField("tile_x", edge.tile_x);
692 formatter.AddField("tile_y", edge.tile_y);
693 formatter.AddField("is_exit", edge.is_exit);
694 formatter.EndObject();
695 }
696 formatter.EndArray();
697
698 // Stair/holewarp edges
699 formatter.BeginArray("stair_edges");
700 for (const auto& edge : stair_edges) {
701 formatter.BeginObject();
702 formatter.AddField("from", absl::StrFormat("0x%02X", edge.from_room));
703 formatter.AddField("to", absl::StrFormat("0x%02X", edge.to_room));
704 formatter.AddField("type", edge.type);
705 formatter.EndObject();
706 }
707 formatter.EndArray();
708
709 int exit_count = 0;
710 for (const auto& edge : door_edges) {
711 if (edge.is_exit)
712 exit_count++;
713 }
714 formatter.BeginObject("stats");
715 formatter.AddField("door_edges", static_cast<int>(door_edges.size()));
716 formatter.AddField("exit_doors", exit_count);
717 formatter.AddField("stair_edges", static_cast<int>(stair_edges.size()));
718 formatter.EndObject();
719
720 formatter.EndObject();
721 return absl::OkStatus();
722}
723
724} // namespace handlers
725} // namespace cli
726} // 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:28
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
Dungeon Room Entrance or Spawn Point.
uint8_t holewarp() const
Definition room.h:573
uint8_t blockset() const
Definition room.h:569
uint8_t staircase_room(int index) const
Definition room.h:562
const std::vector< Door > & GetDoors() const
Definition room.h:294
bool RoomHasDoorIn(Rom *rom, int room_id, zelda3::DoorDirection dir)
bool ParseHexString(absl::string_view str, int *out)
Definition hex_util.h:17
DoorType
Door types from ALTTP.
Definition door_types.h:33
@ FancyDungeonExitLower
Fancy dungeon exit (lower layer)
@ FancyDungeonExit
Fancy dungeon exit.
@ ExitLower
Exit (lower layer)
@ BombableCaveExit
Bombable cave exit.
@ UnusedCaveExit
Unused cave exit (lower layer)
@ LitCaveExitLower
Lit cave exit (lower layer)
@ WaterfallDoor
Waterfall door.
@ ExitMarker
Exit marker.
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:274
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:253
constexpr std::array< std::string_view, 297 > kRoomNames
Definition room.h:791
constexpr int kNumberOfRooms
DoorDirection
Door direction on room walls.
Definition door_types.h:18
@ South
Bottom wall (horizontal door, 4x3 tiles)
@ North
Top wall (horizontal door, 4x3 tiles)
@ East
Right wall (vertical door, 3x4 tiles)
@ West
Left wall (vertical door, 3x4 tiles)