yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_group_commands.cc
Go to the documentation of this file.
2
3#include <cstdint>
4#include <map>
5#include <set>
6#include <string>
7#include <vector>
8
9#include "absl/strings/str_format.h"
10#include "cli/util/hex_util.h"
11#include "rom/rom.h"
12#include "zelda3/dungeon/room.h"
14
15namespace yaze {
16namespace cli {
17namespace handlers {
18
20
21namespace {
22
23// ROM addresses for dungeon tables
24constexpr int kEntranceDungeon = 0x1548B; // Dungeon ID per entrance
25constexpr int kDungeonsStartRooms = 0x7939; // Start room per dungeon
26constexpr int kDungeonsEndRooms = 0x792D; // End room per dungeon (unused)
27constexpr int kDungeonsBossRooms = 0x10954; // Boss room per dungeon
28constexpr int kNumberOfDungeons = 14; // Vanilla dungeons
29constexpr int kNumberOfEntrances = 0x84; // Total entrances
30
31// Dungeon names (vanilla + common custom slots)
32const std::vector<std::string> kDungeonNames = {
33 "Sewers", // 0x00
34 "Hyrule Castle", // 0x01
35 "Eastern Palace", // 0x02
36 "Desert Palace", // 0x03
37 "Agahnim's Tower", // 0x04
38 "Swamp Palace", // 0x05
39 "Palace of Darkness", // 0x06
40 "Misery Mire", // 0x07
41 "Skull Woods", // 0x08
42 "Ice Palace", // 0x09
43 "Tower of Hera", // 0x0A
44 "Thieves' Town", // 0x0B
45 "Turtle Rock", // 0x0C
46 "Ganon's Tower", // 0x0D
47 "Custom Dungeon 0E", // 0x0E
48 "Custom Dungeon 0F", // 0x0F
49 "Custom Dungeon 10", // 0x10
50 "Custom Dungeon 11", // 0x11
51 "Custom Dungeon 12", // 0x12
52 "Custom Dungeon 13", // 0x13
53 "Custom Dungeon 14", // 0x14 (Oracle custom)
54 "Custom Dungeon 15", // 0x15
55 "Custom Dungeon 16", // 0x16
56 "Custom Dungeon 17", // 0x17
57 "Custom Dungeon 18", // 0x18
58 "Custom Dungeon 19", // 0x19
59 "Custom Dungeon 1A", // 0x1A
60 "Custom Dungeon 1B", // 0x1B
61 "Custom Dungeon 1C", // 0x1C
62 "Custom Dungeon 1D", // 0x1D
63 "Custom Dungeon 1E", // 0x1E
64 "Custom Dungeon 1F", // 0x1F
65};
66
68 int id;
69 std::string name;
72 std::set<int> rooms;
73};
74
75std::string GetDungeonName(int id) {
76 if (id >= 0 && id < static_cast<int>(kDungeonNames.size())) {
77 return kDungeonNames[id];
78 }
79 return absl::StrFormat("Dungeon %02X", id);
80}
81
82} // namespace
83
85 Rom* rom, const resources::ArgumentParser& parser,
86 resources::OutputFormatter& formatter) {
87 // Parse optional filters
88 auto dungeon_id_opt = parser.GetString("dungeon");
89 bool list_only = parser.HasFlag("list");
90
91 int dungeon_filter = -1;
92 if (dungeon_id_opt.has_value()) {
93 if (!ParseHexString(dungeon_id_opt.value(), &dungeon_filter)) {
94 return absl::InvalidArgumentError(
95 "Invalid dungeon ID format. Must be hex (e.g., 0x02).");
96 }
97 }
98
99 // Build dungeon info from ROM tables
100 std::map<int, DungeonInfo> dungeons;
101
102 // Read start rooms and boss rooms from ROM
103 for (int i = 0; i < kNumberOfDungeons; ++i) {
104 DungeonInfo info;
105 info.id = i;
106 info.name = GetDungeonName(i);
107
108 // Read start room (1 byte per dungeon)
109 info.start_room = rom->data()[kDungeonsStartRooms + i];
110
111 // Read boss room (2 bytes per dungeon)
112 uint16_t boss_room_addr = kDungeonsBossRooms + (i * 2);
113 info.boss_room = rom->data()[boss_room_addr] |
114 (rom->data()[boss_room_addr + 1] << 8);
115
116 // Boss room 0xFFFF means no boss
117 if (info.boss_room == 0xFFFF) {
118 info.boss_room = -1;
119 }
120
121 dungeons[i] = info;
122 }
123
124 // Scan entrances to find room->dungeon mappings
125 std::map<int, int> room_to_dungeon;
126 for (int entrance_id = 0; entrance_id < kNumberOfEntrances; ++entrance_id) {
127 zelda3::RoomEntrance entrance(rom, static_cast<uint8_t>(entrance_id), false);
128
129 int dungeon_id = entrance.dungeon_id_;
130 int room_id = entrance.room_;
131
132 // Track the room's dungeon assignment
133 room_to_dungeon[room_id] = dungeon_id;
134
135 // Add room to dungeon's room set
136 if (dungeons.find(dungeon_id) == dungeons.end()) {
137 // Create entry for custom dungeons discovered via entrances
138 DungeonInfo info;
139 info.id = dungeon_id;
140 info.name = GetDungeonName(dungeon_id);
141 info.start_room = -1;
142 info.boss_room = -1;
143 dungeons[dungeon_id] = info;
144 }
145 dungeons[dungeon_id].rooms.insert(room_id);
146 }
147
148 // Also scan spawn points for additional room mappings
149 for (int spawn_id = 0; spawn_id < 0x14; ++spawn_id) {
150 zelda3::RoomEntrance spawn(rom, static_cast<uint8_t>(spawn_id), true);
151
152 int dungeon_id = spawn.dungeon_id_;
153 int room_id = spawn.room_;
154
155 room_to_dungeon[room_id] = dungeon_id;
156
157 if (dungeons.find(dungeon_id) != dungeons.end()) {
158 dungeons[dungeon_id].rooms.insert(room_id);
159 }
160 }
161
162 // Find rooms not assigned to any dungeon
163 std::set<int> unassigned_rooms;
164 for (int room_id = 0; room_id < zelda3::kNumberOfRooms; ++room_id) {
165 if (room_to_dungeon.find(room_id) == room_to_dungeon.end()) {
166 unassigned_rooms.insert(room_id);
167 }
168 }
169
170 // Output
171 formatter.BeginObject("dungeon_groups");
172
173 // List mode - compact dungeon listing
174 if (list_only) {
175 formatter.BeginArray("dungeons");
176 for (const auto& [id, info] : dungeons) {
177 if (dungeon_filter >= 0 && id != dungeon_filter) {
178 continue;
179 }
180 formatter.BeginObject();
181 formatter.AddField("id", absl::StrFormat("0x%02X", id));
182 formatter.AddField("name", info.name);
183 formatter.AddField("room_count", static_cast<int>(info.rooms.size()));
184 formatter.EndObject();
185 }
186 formatter.EndArray();
187 } else {
188 // Full output with room lists
189 formatter.BeginArray("dungeons");
190 for (const auto& [id, info] : dungeons) {
191 if (dungeon_filter >= 0 && id != dungeon_filter) {
192 continue;
193 }
194
195 // Skip empty dungeons unless specifically requested
196 if (info.rooms.empty() && dungeon_filter < 0) {
197 continue;
198 }
199
200 formatter.BeginObject();
201 formatter.AddField("id", absl::StrFormat("0x%02X", id));
202 formatter.AddField("name", info.name);
203
204 if (info.start_room >= 0) {
205 formatter.AddField("start_room",
206 absl::StrFormat("0x%02X", info.start_room));
207 } else {
208 formatter.AddField("start_room", "null");
209 }
210
211 if (info.boss_room >= 0) {
212 formatter.AddField("boss_room",
213 absl::StrFormat("0x%02X", info.boss_room));
214 } else {
215 formatter.AddField("boss_room", "null");
216 }
217
218 // Room list
219 formatter.BeginArray("rooms");
220 for (int room_id : info.rooms) {
221 formatter.BeginObject();
222 formatter.AddField("id", absl::StrFormat("0x%02X", room_id));
223 // Bounds check for kRoomNames (array size is 297)
224 if (room_id >= 0 && room_id < 297) {
225 formatter.AddField("name", std::string(zelda3::kRoomNames[room_id]));
226 } else {
227 formatter.AddField("name", absl::StrFormat("Room 0x%02X", room_id));
228 }
229 formatter.EndObject();
230 }
231 formatter.EndArray();
232
233 formatter.EndObject();
234 }
235 formatter.EndArray();
236
237 // Unassigned rooms (only in full output)
238 if (dungeon_filter < 0 && !unassigned_rooms.empty()) {
239 formatter.BeginArray("unassigned_rooms");
240 for (int room_id : unassigned_rooms) {
241 formatter.AddArrayItem(absl::StrFormat("0x%02X", room_id));
242 }
243 formatter.EndArray();
244 }
245 }
246
247 // Statistics
248 formatter.BeginObject("stats");
249 int total_assigned = 0;
250 for (const auto& [id, info] : dungeons) {
251 total_assigned += static_cast<int>(info.rooms.size());
252 }
253 formatter.AddField("total_dungeons", static_cast<int>(dungeons.size()));
254 formatter.AddField("assigned_rooms", total_assigned);
255 formatter.AddField("unassigned_rooms",
256 static_cast<int>(unassigned_rooms.size()));
257 formatter.EndObject();
258
259 formatter.EndObject();
260
261 return absl::OkStatus();
262}
263
264} // namespace handlers
265} // namespace cli
266} // 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
auto data() const
Definition rom.h:139
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.
bool ParseHexString(absl::string_view str, int *out)
Definition hex_util.h:17
constexpr std::array< std::string_view, 297 > kRoomNames
Definition room.h:791
constexpr int kNumberOfRooms
constexpr int kEntranceDungeon