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