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