yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_commands.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <fstream>
5#include <limits>
6#include <memory>
7#include <optional>
8#include <sstream>
9#include <vector>
10
11#include "absl/strings/ascii.h"
12#include "absl/strings/str_cat.h"
13#include "absl/strings/str_format.h"
14#include "absl/strings/str_split.h"
15#include "cli/util/hex_util.h"
17#include "core/hack_manifest.h"
18#include "rom/rom.h"
19#include "rom/snes.h"
20#include "rom/transaction.h"
21#include "util/macro.h"
26#include "zelda3/dungeon/room.h"
31
32namespace yaze {
33namespace cli {
34namespace handlers {
35
37
38namespace {
39
40constexpr uint8_t kStopTileMin = 0xB7;
41constexpr uint8_t kStopTileMax = 0xBA;
42
43bool IsStopTile(uint8_t v) {
44 return v >= kStopTileMin && v <= kStopTileMax;
45}
46
48 // Lightable torches and pushable blocks are synthesized from separate
49 // global tables after the encoded room object stream has been parsed.
50 return (object.options() & zelda3::ObjectOption::Torch) ==
52 (object.options() & zelda3::ObjectOption::Block) ==
54}
55
60
61absl::StatusOr<std::optional<RoomPropertyManifestContext>>
63 const auto manifest_path = parser.GetString("manifest");
64 if (!manifest_path.has_value()) {
65 return std::nullopt;
66 }
67
69 RETURN_IF_ERROR(context.manifest.LoadFromFile(*manifest_path));
70 const auto* manifest_layout = context.manifest.GetDungeonStreamLayout(
72 if (manifest_layout == nullptr) {
73 return absl::FailedPreconditionError(
74 "Manifest does not define dungeon_stream_regions.objects");
75 }
76 if (manifest_layout->strategy != core::DungeonWriteStrategy::kCopyOnWrite) {
77 return absl::FailedPreconditionError(
78 "dungeon_stream_regions.objects must use copy_on_write");
79 }
82 core::DungeonStreamType::kObjects, *manifest_layout));
83 return std::optional<RoomPropertyManifestContext>(std::move(context));
84}
85
87 const Rom& rom, int room_id, const RoomPropertyManifestContext& context) {
88 std::vector<std::pair<uint32_t, uint32_t>> ranges;
89
90 uint32_t pointer_table_snes = 0;
91 ASSIGN_OR_RETURN(pointer_table_snes,
93 const uint32_t pointer_table_pc = SnesToPc(pointer_table_snes);
94 const uint64_t pointer_slot = static_cast<uint64_t>(pointer_table_pc) +
95 static_cast<uint64_t>(room_id) * 3u;
96 if (pointer_slot + 3u > rom.size()) {
97 return absl::OutOfRangeError(
98 "Selected object pointer slot is outside the ROM");
99 }
100
101 uint32_t stream_snes = 0;
102 ASSIGN_OR_RETURN(stream_snes, rom.ReadLong(static_cast<int>(pointer_slot)));
103 const uint32_t stream_pc = SnesToPc(stream_snes);
104 if (static_cast<uint64_t>(stream_pc) + 2u > rom.size()) {
105 return absl::OutOfRangeError(
106 "Selected object stream header is outside the ROM");
107 }
108 ranges.emplace_back(stream_pc, stream_pc + 2u);
109
110 for (const auto& range : context.allocator_layout.allocation_ranges) {
111 ranges.emplace_back(range.begin, range.end);
112 }
113 const uint64_t cow_pointer_slot =
114 static_cast<uint64_t>(context.allocator_layout.pointer_table_pc) +
115 static_cast<uint64_t>(room_id) * 3u;
116 if (cow_pointer_slot + 3u > std::numeric_limits<uint32_t>::max()) {
117 return absl::OutOfRangeError("Selected COW pointer range overflows");
118 }
119 ranges.emplace_back(static_cast<uint32_t>(cow_pointer_slot),
120 static_cast<uint32_t>(cow_pointer_slot + 3u));
121
122 const uint64_t door_pointer_slot =
123 static_cast<uint64_t>(zelda3::kDoorPointers) +
124 static_cast<uint64_t>(room_id) * 3u;
125 if (door_pointer_slot + 3u > std::numeric_limits<uint32_t>::max()) {
126 return absl::OutOfRangeError("Selected door pointer range overflows");
127 }
128 ranges.emplace_back(static_cast<uint32_t>(door_pointer_slot),
129 static_cast<uint32_t>(door_pointer_slot + 3u));
130
131 if (!context.manifest.AnalyzePcWriteRanges(ranges).empty()) {
132 return absl::PermissionDeniedError("Write conflict with Hack Manifest");
133 }
134 return absl::OkStatus();
135}
136
137// Merge stop tiles from |existing| into |generated| without overwriting
138// track tiles that were just produced. Stop tiles in |existing| that sit on
139// cells that are zero in |generated| are copied across unchanged.
141 zelda3::CustomCollisionMap& generated) {
142 for (int i = 0; i < static_cast<int>(existing.tiles.size()); ++i) {
143 if (IsStopTile(existing.tiles[i]) && generated.tiles[i] == 0) {
144 generated.tiles[i] = existing.tiles[i];
145 }
146 }
147}
148
149absl::Status SaveTrackCollisionRom(Rom* rom,
150 const resources::ArgumentParser& parser,
151 resources::OutputFormatter& formatter) {
152 if (parser.HasFlag("mock-rom")) {
153 formatter.AddField("save_status", "mock-rom-skipped");
154 return absl::OkStatus();
155 }
156
157 Rom::SaveSettings save_settings;
158 save_settings.require_backup = true;
159 auto save_status = rom->SaveToFile(save_settings);
160 if (!save_status.ok()) {
161 formatter.AddField("save_error", std::string(save_status.message()));
162 return save_status;
163 }
164
165 formatter.AddField("save_status", "saved");
166 return absl::OkStatus();
167}
168
169// Helper to load sprite registry from file if --sprite-registry flag is provided
171 auto registry_path = parser.GetString("sprite-registry");
172 if (!registry_path.has_value()) {
173 return absl::OkStatus(); // Flag not provided, nothing to load
174 }
175
176 std::ifstream file(registry_path.value());
177 if (!file.is_open()) {
178 return absl::NotFoundError(absl::StrFormat(
179 "Could not open sprite registry: %s", registry_path.value()));
180 }
181
182 std::stringstream buffer;
183 buffer << file.rdbuf();
185}
186
187} // namespace
188
189absl::Status WriteDungeonSpawnPointReport(Rom* rom, int spawn_id,
191 std::string_view object_title) {
192 if (spawn_id < 0 || spawn_id >= zelda3::kNumDungeonSpawnPoints) {
193 return absl::InvalidArgumentError(absl::StrFormat(
194 "Dungeon spawn point ID %d out of range (0x00-0x06).", spawn_id));
195 }
196
197 auto spawn_or = zelda3::DungeonSpawnPoint::Load(*rom, spawn_id);
198 if (!spawn_or.ok()) {
199 return spawn_or.status();
200 }
201 const auto& spawn = spawn_or.value();
202
203 if (!object_title.empty()) {
204 formatter.BeginObject(std::string(object_title));
205 }
206
207 formatter.AddHexField("spawn_id", spawn_id, 2);
208 formatter.AddField("is_spawn_point", true);
209 formatter.AddHexField("room_id", spawn.room_id, 4);
210 formatter.AddHexField("dungeon_id", spawn.dungeon_id, 2);
211 formatter.AddHexField("quadrant", spawn.quadrant, 2);
212 formatter.AddHexField("overworld_door_tilemap", spawn.overworld_door_tilemap,
213 4);
214 formatter.AddHexField("entrance_id", spawn.entrance_id, 4);
215
216 formatter.BeginObject("position");
217 formatter.AddField("x", static_cast<int>(spawn.x_coordinate));
218 formatter.AddField("y", static_cast<int>(spawn.y_coordinate));
219 formatter.EndObject();
220
221 formatter.BeginObject("camera");
222 formatter.AddField("horizontal_scroll",
223 static_cast<int>(spawn.horizontal_scroll));
224 formatter.AddField("vertical_scroll",
225 static_cast<int>(spawn.vertical_scroll));
226 formatter.AddField("trigger_x", static_cast<int>(spawn.camera_trigger_x));
227 formatter.AddField("trigger_y", static_cast<int>(spawn.camera_trigger_y));
228 formatter.EndObject();
229
230 formatter.BeginObject("properties");
231 formatter.AddHexField("main_gfx", spawn.main_gfx, 2);
232 formatter.AddHexField("floor", spawn.floor, 2);
233 formatter.AddHexField("layer", spawn.layer, 2);
234 formatter.AddHexField("camera_scroll_controller",
235 spawn.camera_scroll_controller, 2);
236 formatter.AddHexField("song", spawn.song, 2);
237 formatter.EndObject();
238
239 formatter.BeginObject("camera_boundaries");
240 formatter.AddHexField("qn", spawn.camera_scroll_boundaries[0], 2);
241 formatter.AddHexField("fn", spawn.camera_scroll_boundaries[1], 2);
242 formatter.AddHexField("qs", spawn.camera_scroll_boundaries[2], 2);
243 formatter.AddHexField("fs", spawn.camera_scroll_boundaries[3], 2);
244 formatter.AddHexField("qw", spawn.camera_scroll_boundaries[4], 2);
245 formatter.AddHexField("fw", spawn.camera_scroll_boundaries[5], 2);
246 formatter.AddHexField("qe", spawn.camera_scroll_boundaries[6], 2);
247 formatter.AddHexField("fe", spawn.camera_scroll_boundaries[7], 2);
248 formatter.EndObject();
249
250 if (!object_title.empty()) {
251 formatter.EndObject();
252 }
253 return absl::OkStatus();
254}
255
257 Rom* rom, const resources::ArgumentParser& parser,
258 resources::OutputFormatter& formatter) {
259 // Load custom sprite registry if provided (e.g., Oracle of Secrets)
260 auto registry_status = MaybeLoadSpriteRegistry(parser);
261 if (!registry_status.ok()) {
262 return registry_status;
263 }
264
265 auto room_id_str = parser.GetString("room").value();
266
267 int room_id;
268 if (!ParseHexString(room_id_str, &room_id)) {
269 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
270 }
271
272 formatter.BeginObject("Dungeon Room Sprites");
273 formatter.AddField("room_id", room_id);
274
275 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
276 room.LoadSprites();
277 const auto& sprites = room.GetSprites();
278
279 formatter.AddField("total_sprites", static_cast<int>(sprites.size()));
280 formatter.AddField("status", "success");
281
282 formatter.BeginArray("sprites");
283 for (const auto& sprite : sprites) {
284 formatter.BeginObject();
285 formatter.AddHexField("sprite_id", sprite.id(), 2);
286 formatter.AddField("name", zelda3::ResolveSpriteName(sprite.id()));
287 formatter.AddField("x", sprite.x());
288 formatter.AddField("y", sprite.y());
289 formatter.AddField("subtype", sprite.subtype());
290 formatter.AddField("layer", sprite.layer());
291 if (sprite.key_drop() > 0) {
292 formatter.AddField("key_drop", sprite.key_drop());
293 }
294 formatter.EndObject();
295 }
296 formatter.EndArray();
297 formatter.EndObject();
298
299 return absl::OkStatus();
300}
301
303 Rom* rom, const resources::ArgumentParser& parser,
304 resources::OutputFormatter& formatter) {
305 // Load custom sprite registry if provided (e.g., Oracle of Secrets)
306 auto registry_status = MaybeLoadSpriteRegistry(parser);
307 if (!registry_status.ok()) {
308 return registry_status;
309 }
310
311 auto room_id_str = parser.GetString("room").value();
312
313 int room_id;
314 if (!ParseHexString(room_id_str, &room_id)) {
315 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
316 }
317
318 formatter.AddField("room_id", room_id);
319
320 // Load full room to get objects, doors, and stairs
321 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
322 const bool include_objects = parser.HasFlag("include-objects");
323 const auto& tile_objects = room.GetTileObjects();
324
325 formatter.AddField("status", "success");
326 formatter.AddField("name", absl::StrFormat("Room %d", room.id()));
327 formatter.AddField("room_id", room.id());
328 formatter.AddField("room_type", "Dungeon Room");
329
330 // Room properties from Room data
331 formatter.BeginObject("properties");
332 formatter.AddField("blockset", room.blockset());
333 formatter.AddField("spriteset", room.spriteset());
334 formatter.AddField("palette", room.palette());
335 formatter.AddField("layout", room.layout_id());
336 formatter.AddField("floor1", room.floor1());
337 formatter.AddField("floor2", room.floor2());
338 formatter.AddField("effect", static_cast<int>(room.effect()));
339 formatter.AddField("tag1", static_cast<int>(room.tag1()));
340 formatter.AddField("tag2", static_cast<int>(room.tag2()));
341
342 // Check object counts for simple heuristics
343 const int object_count = include_objects
344 ? static_cast<int>(std::count_if(
345 tile_objects.begin(), tile_objects.end(),
346 IsEncodedRoomStreamObject))
347 : static_cast<int>(tile_objects.size());
348 formatter.AddField("object_count", object_count);
349
350 formatter.EndObject();
351
352 if (include_objects) {
353 formatter.BeginArray("objects");
354 int stream_index = 0;
355 for (const auto& object : tile_objects) {
356 if (!IsEncodedRoomStreamObject(object)) {
357 continue;
358 }
359
360 const auto encoded = object.EncodeObjectToBytes();
361 formatter.BeginObject();
362 formatter.AddHexField("object_id", static_cast<uint16_t>(object.id_), 3);
364 encoded.b1, encoded.b3));
365 formatter.AddField("x", static_cast<int>(object.x()));
366 formatter.AddField("y", static_cast<int>(object.y()));
367 formatter.AddField("size", static_cast<int>(object.size()));
368 formatter.AddField("stream_index", stream_index++);
369 formatter.EndObject();
370 }
371 formatter.EndArray();
372 }
373
374 // Export Doors
375 formatter.BeginArray("doors");
376 for (const auto& door : room.GetDoors()) {
377 formatter.BeginObject();
378 formatter.AddField("position", door.position);
379 formatter.AddField("direction", std::string(door.GetDirectionName()));
380 formatter.AddField("type", std::string(door.GetTypeName()));
381 auto [tx, ty] = door.GetTileCoords();
382 formatter.AddField("tile_x", tx);
383 formatter.AddField("tile_y", ty);
384 formatter.EndObject();
385 }
386 formatter.EndArray();
387
388 // Export Staircases
389 formatter.BeginArray("staircases");
390 for (const auto& stair : room.GetStairs()) {
391 formatter.BeginObject();
392 formatter.AddField("tile_x",
393 stair.id); // 'id' field stores X in struct staircase
394 formatter.AddField(
395 "tile_y", stair.room); // 'room' field stores Y in struct staircase
396 formatter.AddField("label", stair.label);
397 formatter.EndObject();
398 }
399 formatter.EndArray();
400
401 // Export Chests
402 formatter.BeginArray("chests");
403 for (const auto& chest : room.GetChests()) {
404 formatter.BeginObject();
405 formatter.AddHexField("item_id", chest.id, 2);
406 formatter.AddField("item_name", zelda3::GetItemLabel(chest.id));
407 formatter.AddField("is_big_chest", chest.size);
408 formatter.EndObject();
409 }
410 formatter.EndArray();
411
412 return absl::OkStatus();
413}
414
416 Rom* rom, const resources::ArgumentParser& parser,
417 resources::OutputFormatter& formatter) {
418 auto room_id_opt = parser.GetString("room");
419
420 bool has_room_filter = room_id_opt.has_value();
421 int room_filter = -1;
422 if (has_room_filter) {
423 if (!ParseHexString(room_id_opt.value(), &room_filter)) {
424 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
425 }
426 }
427
428 int total_rooms = 0;
429 int rooms_with_chests = 0;
430 int total_chests = 0;
431 std::map<int, int> item_counts;
432
433 formatter.BeginObject("Dungeon Chests");
434 formatter.AddField("room_filter", has_room_filter
435 ? absl::StrFormat("0x%02X", room_filter)
436 : "all");
437
438 formatter.BeginArray("rooms");
439 int start_room = has_room_filter ? room_filter : 0;
440 int end_room = has_room_filter ? room_filter : zelda3::kNumberOfRooms - 1;
441
442 for (int room_id = start_room; room_id <= end_room; ++room_id) {
443 total_rooms++;
444 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
445 room.LoadChests();
446
447 const auto& chests = room.GetChests();
448 if (chests.empty()) {
449 continue;
450 }
451
452 rooms_with_chests++;
453 total_chests += static_cast<int>(chests.size());
454
455 formatter.BeginObject();
456 formatter.AddField("room_id", absl::StrFormat("0x%02X", room_id));
457 formatter.AddField("room_name", zelda3::GetRoomLabel(room_id));
458 formatter.AddField("chest_count", static_cast<int>(chests.size()));
459
460 formatter.BeginArray("chests");
461 int chest_index = 0;
462 for (const auto& chest : chests) {
463 formatter.BeginObject();
464 formatter.AddField("index", chest_index++);
465 formatter.AddHexField("item_id", chest.id, 2);
466 formatter.AddField("item_name", zelda3::GetItemLabel(chest.id));
467 formatter.AddField("is_big_chest", chest.size);
468 formatter.EndObject();
469
470 if (chest.id != 0) {
471 item_counts[chest.id]++;
472 }
473 }
474 formatter.EndArray();
475 formatter.EndObject();
476 }
477 formatter.EndArray();
478
479 formatter.BeginObject("summary");
480 formatter.AddField("total_rooms", total_rooms);
481 formatter.AddField("rooms_with_chests", rooms_with_chests);
482 formatter.AddField("total_chests", total_chests);
483 formatter.AddField("unique_items", static_cast<int>(item_counts.size()));
484
485 formatter.BeginArray("duplicate_items");
486 for (const auto& [item_id, count] : item_counts) {
487 if (count < 2) {
488 continue;
489 }
490 formatter.BeginObject();
491 formatter.AddHexField("item_id", item_id, 2);
492 formatter.AddField("item_name", zelda3::GetItemLabel(item_id));
493 formatter.AddField("count", count);
494 formatter.EndObject();
495 }
496 formatter.EndArray();
497 formatter.EndObject();
498
499 formatter.EndObject();
500 return absl::OkStatus();
501}
502
504 Rom* rom, const resources::ArgumentParser& parser,
505 resources::OutputFormatter& formatter) {
506 auto entrance_id_str = parser.GetString("entrance").value();
507 bool is_spawn_point = parser.HasFlag("spawn");
508
509 int entrance_id;
510 if (!ParseHexString(entrance_id_str, &entrance_id)) {
511 return absl::InvalidArgumentError(
512 "Invalid entrance ID format. Must be hex.");
513 }
514
515 if (is_spawn_point) {
516 return WriteDungeonSpawnPointReport(rom, entrance_id, formatter, "");
517 }
518
519 zelda3::RoomEntrance entrance(rom, static_cast<uint8_t>(entrance_id), false);
520
521 formatter.AddField("entrance_id", absl::StrFormat("0x%02X", entrance_id));
522 formatter.AddField("is_spawn_point", is_spawn_point);
523 formatter.AddField("room_id", absl::StrFormat("0x%04X", entrance.room_));
524 formatter.AddField("exit_id", absl::StrFormat("0x%04X", entrance.exit_));
525
526 formatter.BeginObject("position");
527 formatter.AddField("x", entrance.x_position_);
528 formatter.AddField("y", entrance.y_position_);
529 formatter.EndObject();
530
531 formatter.BeginObject("camera");
532 formatter.AddField("x", entrance.camera_x_);
533 formatter.AddField("y", entrance.camera_y_);
534 formatter.AddField("trigger_x", entrance.camera_trigger_x_);
535 formatter.AddField("trigger_y", entrance.camera_trigger_y_);
536 formatter.EndObject();
537
538 formatter.BeginObject("properties");
539 formatter.AddField("blockset", absl::StrFormat("0x%02X", entrance.blockset_));
540 formatter.AddField("floor", absl::StrFormat("0x%02X", entrance.floor_));
541 formatter.AddField("dungeon_id",
542 absl::StrFormat("0x%02X", entrance.dungeon_id_));
543 formatter.AddField("door", absl::StrFormat("0x%02X", entrance.door_));
544 formatter.AddField("ladder_bg",
545 absl::StrFormat("0x%02X", entrance.ladder_bg_));
546 formatter.AddField("scrolling",
547 absl::StrFormat("0x%02X", entrance.scrolling_));
548 formatter.AddField("scroll_quadrant",
549 absl::StrFormat("0x%02X", entrance.scroll_quadrant_));
550 formatter.AddField("music", absl::StrFormat("0x%02X", entrance.music_));
551 formatter.EndObject();
552
553 formatter.BeginObject("camera_boundaries");
554 formatter.AddField("qn",
555 absl::StrFormat("0x%02X", entrance.camera_boundary_qn_));
556 formatter.AddField("fn",
557 absl::StrFormat("0x%02X", entrance.camera_boundary_fn_));
558 formatter.AddField("qs",
559 absl::StrFormat("0x%02X", entrance.camera_boundary_qs_));
560 formatter.AddField("fs",
561 absl::StrFormat("0x%02X", entrance.camera_boundary_fs_));
562 formatter.AddField("qw",
563 absl::StrFormat("0x%02X", entrance.camera_boundary_qw_));
564 formatter.AddField("fw",
565 absl::StrFormat("0x%02X", entrance.camera_boundary_fw_));
566 formatter.AddField("qe",
567 absl::StrFormat("0x%02X", entrance.camera_boundary_qe_));
568 formatter.AddField("fe",
569 absl::StrFormat("0x%02X", entrance.camera_boundary_fe_));
570 formatter.EndObject();
571
572 return absl::OkStatus();
573}
574
576 Rom* rom, const resources::ArgumentParser& parser,
577 resources::OutputFormatter& formatter) {
578 auto room_id_str = parser.GetString("room").value();
579
580 int room_id;
581 if (!ParseHexString(room_id_str, &room_id)) {
582 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
583 }
584
585 formatter.BeginObject("Dungeon Export");
586 formatter.AddField("room_id", room_id);
587
588 // Use existing dungeon system
589 zelda3::DungeonEditorSystem dungeon_editor(rom);
590 auto room_or = dungeon_editor.GetRoom(room_id);
591 if (!room_or.ok()) {
592 formatter.AddField("status", "error");
593 formatter.AddField("error", room_or.status().ToString());
594 formatter.EndObject();
595 return room_or.status();
596 }
597
598 auto& room = room_or.value();
599
600 // Export room data
601 formatter.AddField("status", "success");
602 formatter.AddField("room_width", "Unknown");
603 formatter.AddField("room_height", "Unknown");
604 formatter.AddField("room_name", absl::StrFormat("Room %d", room.id()));
605
606 // Add room data as JSON
607 formatter.BeginObject("room_data");
608 formatter.AddField("tiles", "Room tile data would be exported here");
609 formatter.AddField("sprites", "Room sprite data would be exported here");
610 formatter.AddField("doors", "Room door data would be exported here");
611 formatter.EndObject();
612
613 formatter.EndObject();
614
615 return absl::OkStatus();
616}
617
619 Rom* rom, const resources::ArgumentParser& parser,
620 resources::OutputFormatter& formatter) {
621 auto room_id_str = parser.GetString("room").value();
622
623 int room_id;
624 if (!ParseHexString(room_id_str, &room_id)) {
625 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
626 }
627
628 formatter.BeginObject("Dungeon Room Objects");
629 formatter.AddField("room_id", room_id);
630
631 // Use existing dungeon system
632 zelda3::DungeonEditorSystem dungeon_editor(rom);
633 auto room_or = dungeon_editor.GetRoom(room_id);
634 if (!room_or.ok()) {
635 formatter.AddField("status", "error");
636 formatter.AddField("error", room_or.status().ToString());
637 formatter.EndObject();
638 return room_or.status();
639 }
640
641 auto& room = room_or.value();
642
643 // Load objects if not already loaded (GetTileObjects might be empty otherwise)
644 room.LoadObjects();
645
646 const auto& objects = room.GetTileObjects();
647 formatter.AddField("total_objects", static_cast<int>(objects.size()));
648 formatter.AddField("status", "success");
649
650 formatter.BeginArray("objects");
651 for (const auto& obj : objects) {
652 formatter.BeginObject("");
653 formatter.AddField("id", obj.id_);
654 formatter.AddField("id_hex", absl::StrFormat("0x%04X", obj.id_));
655 formatter.AddField("x", obj.x_);
656 formatter.AddField("y", obj.y_);
657 formatter.AddField("size", obj.size_);
658 formatter.AddField("layer", static_cast<int>(obj.layer_));
659 // Add decoded type info if available
660 int type = zelda3::RoomObject::DetermineObjectType((obj.id_ & 0xFF),
661 (obj.id_ >> 8));
662 formatter.AddField("type", type);
663 formatter.EndObject();
664 }
665 formatter.EndArray();
666 formatter.EndObject();
667
668 return absl::OkStatus();
669}
670
672 Rom* rom, const resources::ArgumentParser& parser,
673 resources::OutputFormatter& formatter) {
674 auto room_id_str = parser.GetString("room").value();
675
676 int room_id;
677 if (!ParseHexString(room_id_str, &room_id)) {
678 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
679 }
680
681 formatter.BeginObject("Dungeon Room Tiles");
682 formatter.AddField("room_id", room_id);
683
684 // Use existing dungeon system
685 zelda3::DungeonEditorSystem dungeon_editor(rom);
686 auto room_or = dungeon_editor.GetRoom(room_id);
687 if (!room_or.ok()) {
688 formatter.AddField("status", "error");
689 formatter.AddField("error", room_or.status().ToString());
690 formatter.EndObject();
691 return room_or.status();
692 }
693
694 auto& room = room_or.value();
695
696 room.LoadObjects();
697
698 formatter.AddField("room_width", 64);
699 formatter.AddField("room_height", 64);
700 formatter.AddField("total_tiles", 64 * 64);
701 formatter.AddField("has_custom_collision", room.has_custom_collision());
702 formatter.AddField("object_count",
703 static_cast<int>(room.GetTileObjects().size()));
704 formatter.AddField("status", "success");
705
706 // Emit tile rows as compact hex strings to keep output manageable while still
707 // returning deterministic room-tile data.
708 formatter.BeginArray("collision_rows");
709 const auto& collision = room.custom_collision().tiles;
710 for (int y = 0; y < 64; ++y) {
711 std::string row;
712 row.reserve(64 * 3);
713 for (int x = 0; x < 64; ++x) {
714 if (!row.empty()) {
715 row.push_back(' ');
716 }
717 absl::StrAppend(&row, absl::StrFormat("%02X", collision[y * 64 + x]));
718 }
719 formatter.AddArrayItem(row);
720 }
721 formatter.EndArray();
722 formatter.EndObject();
723
724 return absl::OkStatus();
725}
726
728 Rom* rom, const resources::ArgumentParser& parser,
729 resources::OutputFormatter& formatter) {
730 auto room_id_str = parser.GetString("room").value();
731 auto property = parser.GetString("property").value();
732 auto value = parser.GetString("value").value();
733
734 int room_id;
735 if (!ParseHexString(room_id_str, &room_id)) {
736 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
737 }
738
739 formatter.BeginObject("Dungeon Room Property Set");
740 formatter.AddField("room_id", room_id);
741 formatter.AddField("property", property);
742 formatter.AddField("value", value);
743
744 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
745 const absl::Status room_status =
746 absl::InvalidArgumentError("Invalid room ID");
747 formatter.AddField("status", "error");
748 formatter.AddField("error", room_status.ToString());
749 formatter.EndObject();
750 return room_status;
751 }
752
753 int parsed_value = 0;
754 if (!ParseHexString(value, &parsed_value)) {
755 const absl::Status value_status =
756 absl::InvalidArgumentError(absl::StrFormat(
757 "Invalid value format: %s (expected integer/hex)", value));
758 formatter.AddField("status", "error");
759 formatter.AddField("error", std::string(value_status.message()));
760 formatter.EndObject();
761 return value_status;
762 }
763
764 const std::string prop = absl::AsciiStrToLower(property);
765 const bool is_layout = prop == "layout" || prop == "layout_id";
766 const bool is_floor = prop == "floor1" || prop == "floor2";
767 const bool is_object_stream_header = is_layout || is_floor;
768 const int property_max = is_layout ? 0x07 : (is_floor ? 0x0F : 0xFF);
769 if (is_object_stream_header &&
770 (parsed_value < 0 || parsed_value > property_max)) {
771 const absl::Status property_status =
772 absl::InvalidArgumentError(absl::StrFormat(
773 "Property %s must be in range 0..%d", property, property_max));
774 formatter.AddField("status", "error");
775 formatter.AddField("error", std::string(property_status.message()));
776 formatter.EndObject();
777 return property_status;
778 }
779
780 std::optional<RoomPropertyManifestContext> manifest_context;
781 if (is_object_stream_header) {
782 auto context_or = LoadRoomPropertyManifestContext(parser);
783 if (!context_or.ok()) {
784 formatter.AddField("status", "error");
785 formatter.AddField("error", std::string(context_or.status().message()));
786 formatter.EndObject();
787 return context_or.status();
788 }
789 manifest_context = std::move(context_or).value();
790 if (manifest_context.has_value()) {
791 const absl::Status manifest_status =
792 ValidateRoomPropertyManifestConflicts(*rom, room_id,
793 *manifest_context);
794 if (!manifest_status.ok()) {
795 formatter.AddField("status", "error");
796 formatter.AddField("error", std::string(manifest_status.message()));
797 formatter.EndObject();
798 return manifest_status;
799 }
800 formatter.AddField("manifest", *parser.GetString("manifest"));
801 formatter.AddField("allocator_capability", "copy_on_write");
802 }
803 }
804
805 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
806 ScopedRomTransaction transaction(*rom);
807 if (prop == "palette") {
808 room.SetPalette(static_cast<uint8_t>(parsed_value & 0xFF));
809 } else if (prop == "blockset") {
810 room.SetBlockset(static_cast<uint8_t>(parsed_value & 0xFF));
811 } else if (prop == "spriteset") {
812 room.SetSpriteset(static_cast<uint8_t>(parsed_value & 0xFF));
813 } else if (is_layout) {
814 room.SetLayoutId(static_cast<uint8_t>(parsed_value));
815 } else if (prop == "floor1") {
816 room.set_floor1(static_cast<uint8_t>(parsed_value));
817 } else if (prop == "floor2") {
818 room.set_floor2(static_cast<uint8_t>(parsed_value));
819 } else if (prop == "effect") {
820 room.SetEffect(static_cast<zelda3::EffectKey>(parsed_value & 0xFF));
821 } else if (prop == "tag1") {
822 room.SetTag1(static_cast<zelda3::TagKey>(parsed_value & 0xFF));
823 } else if (prop == "tag2") {
824 room.SetTag2(static_cast<zelda3::TagKey>(parsed_value & 0xFF));
825 } else if (prop == "holewarp") {
826 room.SetHolewarp(static_cast<uint8_t>(parsed_value & 0xFF));
827 } else {
828 const absl::Status property_status = absl::InvalidArgumentError(
829 absl::StrFormat("Unsupported property: %s", property));
830 formatter.AddField("status", "error");
831 formatter.AddField("error", std::string(property_status.message()));
832 formatter.EndObject();
833 return property_status;
834 }
835
836 const absl::Status write_status =
837 is_object_stream_header ? room.SaveObjectStreamHeader(
838 manifest_context.has_value()
839 ? &manifest_context->allocator_layout
840 : nullptr)
841 : room.SaveRoomHeader();
842 if (!write_status.ok()) {
843 formatter.AddField("status", "error");
844 formatter.AddField("write_error", std::string(write_status.message()));
845 formatter.EndObject();
846 return write_status;
847 }
848
849 if (parser.HasFlag("mock-rom")) {
850 transaction.Commit();
851 formatter.AddField("save_status", "mock-rom-skipped");
852 } else {
853 Rom::SaveSettings save_settings;
854 save_settings.require_backup = true;
855 const absl::Status save_status = rom->SaveToFile(save_settings);
856 if (!save_status.ok()) {
857 formatter.AddField("status", "error");
858 formatter.AddField("save_error", std::string(save_status.message()));
859 formatter.EndObject();
860 return save_status;
861 }
862 transaction.Commit();
863 formatter.AddField("save_status", "saved");
864 }
865 formatter.AddField("status", "success");
866 formatter.EndObject();
867
868 return absl::OkStatus();
869}
870
872 Rom* rom, const resources::ArgumentParser& parser,
873 resources::OutputFormatter& formatter) {
874 auto room_id_str = parser.GetString("room").value();
875
876 int room_id;
877 if (!ParseHexString(room_id_str, &room_id)) {
878 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
879 }
880
881 formatter.BeginObject("Room Header Debug");
882 formatter.AddField("room_id", room_id);
883 formatter.AddHexField("room_id_hex", room_id, 2);
884
885 // Show ROM address constants
886 formatter.BeginObject("rom_addresses");
887 formatter.AddHexField("kRoomHeaderPointer", zelda3::kRoomHeaderPointer, 4);
888 formatter.AddHexField("kRoomHeaderPointerBank",
890 formatter.EndObject();
891
892 // Read the pointer table address
893 int header_pointer = (rom->data()[zelda3::kRoomHeaderPointer + 2] << 16) +
894 (rom->data()[zelda3::kRoomHeaderPointer + 1] << 8) +
896 int header_pointer_pc = SnesToPc(header_pointer);
897
898 formatter.BeginObject("pointer_table");
899 formatter.AddHexField("snes_address", header_pointer, 6);
900 formatter.AddHexField("pc_address", header_pointer_pc, 6);
901 formatter.EndObject();
902
903 // Read the room's specific header address from the table
904 int table_offset = header_pointer_pc + (room_id * 2);
905 int room_header_addr = (rom->data()[zelda3::kRoomHeaderPointerBank] << 16) +
906 (rom->data()[table_offset + 1] << 8) +
907 rom->data()[table_offset];
908 int room_header_pc = SnesToPc(room_header_addr);
909
910 formatter.BeginObject("room_header_address");
911 formatter.AddHexField("table_offset_pc", table_offset, 6);
912 formatter.AddHexField("snes_address", room_header_addr, 6);
913 formatter.AddHexField("pc_address", room_header_pc, 6);
914 formatter.EndObject();
915
916 // Read and display raw header bytes (14 bytes)
917 formatter.BeginArray("raw_bytes");
918 for (int i = 0; i < 14; ++i) {
919 if (room_header_pc + i < static_cast<int>(rom->size())) {
920 formatter.AddArrayItem(
921 absl::StrFormat("0x%02X", rom->data()[room_header_pc + i]));
922 }
923 }
924 formatter.EndArray();
925
926 // Decode the header bytes
927 if (room_header_pc >= 0 &&
928 room_header_pc + 13 < static_cast<int>(rom->size())) {
929 uint8_t byte0 = rom->data()[room_header_pc];
930 uint8_t byte1 = rom->data()[room_header_pc + 1];
931 uint8_t byte2 = rom->data()[room_header_pc + 2];
932 uint8_t byte3 = rom->data()[room_header_pc + 3];
933
934 formatter.BeginObject("decoded");
935 formatter.AddField("bg2", (byte0 >> 5) & 0x07);
936 formatter.AddField("collision", (byte0 >> 2) & 0x07);
937 formatter.AddField("is_light", (byte0 & 0x01) == 1);
938 formatter.AddField("palette", byte1 & 0x3F);
939 formatter.AddField("blockset", byte2);
940 formatter.AddField("spriteset", byte3);
941 formatter.AddField("effect", rom->data()[room_header_pc + 4]);
942 formatter.AddField("tag1", rom->data()[room_header_pc + 5]);
943 formatter.AddField("tag2", rom->data()[room_header_pc + 6]);
944 formatter.AddField("holewarp", rom->data()[room_header_pc + 9]);
945 formatter.AddField("stair1_room", rom->data()[room_header_pc + 10]);
946 formatter.AddField("stair2_room", rom->data()[room_header_pc + 11]);
947 formatter.AddField("stair3_room", rom->data()[room_header_pc + 12]);
948 formatter.AddField("stair4_room", rom->data()[room_header_pc + 13]);
949 formatter.EndObject();
950 } else {
951 formatter.AddField("error", "Room header address out of range");
952 }
953
954 formatter.EndObject();
955 return absl::OkStatus();
956}
957
959 Rom* rom, const resources::ArgumentParser& parser,
960 resources::OutputFormatter& formatter) {
961 // Build generator options (shared by single and batch modes)
963
964 // Parse --promote-switch X,Y pairs
965 auto switch_str = parser.GetString("promote-switch");
966 if (switch_str.has_value()) {
967 for (absl::string_view pair :
968 absl::StrSplit(switch_str.value(), ' ', absl::SkipEmpty())) {
969 std::vector<std::string> coords =
970 absl::StrSplit(pair, ',', absl::SkipEmpty());
971 if (coords.size() == 2) {
972 int sx, sy;
973 if (ParseHexString(coords[0], &sx) && ParseHexString(coords[1], &sy)) {
974 options.switch_promotions.emplace_back(sx, sy);
975 }
976 }
977 }
978 }
979
980 bool do_write = parser.HasFlag("write");
981 bool do_preserve_stops = parser.HasFlag("preserve-stops");
982 bool do_visualize = parser.HasFlag("visualize");
983
984 // Determine room list: --rooms (batch) or --room (single)
985 std::vector<int> room_ids;
986 auto rooms_arg = parser.GetString("rooms");
987 auto room_arg = parser.GetString("room");
988
989 if (rooms_arg.has_value()) {
990 // Batch mode: parse comma-separated hex room IDs
991 for (absl::string_view token :
992 absl::StrSplit(rooms_arg.value(), ',', absl::SkipEmpty())) {
993 int rid;
994 std::string token_str(token);
995 if (!ParseHexString(token_str, &rid)) {
996 return absl::InvalidArgumentError(absl::StrFormat(
997 "Invalid room ID '%s' in --rooms list. Must be hex.", token_str));
998 }
999 room_ids.push_back(rid);
1000 }
1001 if (room_ids.empty()) {
1002 return absl::InvalidArgumentError("--rooms list is empty.");
1003 }
1004 } else if (room_arg.has_value()) {
1005 // Single room mode (backwards compatible)
1006 int rid;
1007 if (!ParseHexString(room_arg.value(), &rid)) {
1008 return absl::InvalidArgumentError("Invalid room ID format. Must be hex.");
1009 }
1010 room_ids.push_back(rid);
1011 } else {
1012 return absl::InvalidArgumentError("Either --room or --rooms is required.");
1013 }
1014
1015 bool is_batch = room_ids.size() > 1;
1016 std::unique_ptr<ScopedRomTransaction> transaction;
1017 if (do_write) {
1018 transaction = std::make_unique<ScopedRomTransaction>(*rom);
1019 }
1020
1021 if (is_batch) {
1022 // Batch mode: aggregate results with per-room detail
1023 formatter.BeginObject("Batch Track Collision Generation");
1024 formatter.AddField("mode", do_write ? "write" : "dry-run");
1025 formatter.AddField("room_count", static_cast<int>(room_ids.size()));
1026
1027 int total_tiles = 0;
1028 int total_stops = 0;
1029 int total_corners = 0;
1030 int total_switches = 0;
1031 int rooms_succeeded = 0;
1032
1033 formatter.BeginArray("rooms");
1034 for (int room_id : room_ids) {
1035 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
1036 room.LoadObjects();
1037
1038 auto result = zelda3::GenerateTrackCollision(&room, options);
1039 if (!result.ok()) {
1040 // Stop on first error and report which room failed
1041 formatter.EndArray();
1042 formatter.AddHexField("failed_room", room_id, 3);
1043 formatter.AddField("error", std::string(result.status().message()));
1044 formatter.EndObject();
1045 return absl::InternalError(
1046 absl::StrFormat("Generation failed for room 0x%03X: %s", room_id,
1047 result.status().message()));
1048 }
1049
1050 if (do_preserve_stops && do_write) {
1051 auto existing = zelda3::LoadCustomCollisionMap(rom, room_id);
1052 if (existing.ok() && existing->has_data) {
1053 MergeStopTiles(*existing, result->collision_map);
1054 }
1055 }
1056
1057 formatter.BeginObject();
1058 formatter.AddHexField("room_id", room_id, 3);
1059 formatter.AddField("tiles_generated", result->tiles_generated);
1060 formatter.AddField("stop_count", result->stop_count);
1061 formatter.AddField("corner_count", result->corner_count);
1062 formatter.AddField("switch_count", result->switch_count);
1063 if (do_preserve_stops) {
1064 formatter.AddField("stops_preserved", true);
1065 }
1066
1067 if (do_visualize) {
1068 formatter.AddField("visualization", result->ascii_visualization);
1069 }
1070
1071 if (do_write) {
1072 auto write_status =
1073 zelda3::WriteTrackCollision(rom, room_id, result->collision_map);
1074 if (!write_status.ok()) {
1075 // Stop on first write error
1076 formatter.AddField("write_error",
1077 std::string(write_status.message()));
1078 formatter.EndObject();
1079 formatter.EndArray();
1080 formatter.EndObject();
1081 return absl::Status(
1082 write_status.code(),
1083 absl::StrFormat("Write failed for room 0x%03X: %s", room_id,
1084 write_status.message()));
1085 }
1086 formatter.AddField("write_status", "success");
1087 }
1088
1089 total_tiles += result->tiles_generated;
1090 total_stops += result->stop_count;
1091 total_corners += result->corner_count;
1092 total_switches += result->switch_count;
1093 rooms_succeeded++;
1094
1095 formatter.EndObject();
1096 }
1097 formatter.EndArray();
1098
1099 // Save ROM once after all rooms are written
1100 if (do_write) {
1101 auto save_status = SaveTrackCollisionRom(rom, parser, formatter);
1102 if (!save_status.ok()) {
1103 formatter.EndObject();
1104 return save_status;
1105 }
1106 transaction->Commit();
1107 }
1108
1109 // Aggregated totals
1110 formatter.BeginObject("totals");
1111 formatter.AddField("rooms_succeeded", rooms_succeeded);
1112 formatter.AddField("tiles_generated", total_tiles);
1113 formatter.AddField("stop_count", total_stops);
1114 formatter.AddField("corner_count", total_corners);
1115 formatter.AddField("switch_count", total_switches);
1116 formatter.EndObject();
1117
1118 formatter.EndObject();
1119 } else {
1120 // Single room mode (original behavior, backwards compatible)
1121 int room_id = room_ids[0];
1122
1123 zelda3::Room room = zelda3::LoadRoomHeaderFromRom(rom, room_id);
1124 room.LoadObjects();
1125
1126 auto result = zelda3::GenerateTrackCollision(&room, options);
1127 if (!result.ok()) {
1128 return result.status();
1129 }
1130
1131 if (do_preserve_stops && do_write) {
1132 auto existing = zelda3::LoadCustomCollisionMap(rom, room_id);
1133 if (existing.ok() && existing->has_data) {
1134 MergeStopTiles(*existing, result->collision_map);
1135 }
1136 }
1137
1138 formatter.BeginObject("Track Collision Generation");
1139 formatter.AddHexField("room_id", room_id, 3);
1140 formatter.AddField("tiles_generated", result->tiles_generated);
1141 formatter.AddField("stop_count", result->stop_count);
1142 formatter.AddField("corner_count", result->corner_count);
1143 formatter.AddField("switch_count", result->switch_count);
1144 formatter.AddField("mode", do_write ? "write" : "dry-run");
1145 if (do_preserve_stops) {
1146 formatter.AddField("stops_preserved", true);
1147 }
1148
1149 if (do_visualize || !do_write) {
1150 formatter.AddField("visualization", result->ascii_visualization);
1151 }
1152
1153 if (do_write) {
1154 auto write_status =
1155 zelda3::WriteTrackCollision(rom, room_id, result->collision_map);
1156 if (!write_status.ok()) {
1157 formatter.AddField("write_error", std::string(write_status.message()));
1158 formatter.EndObject();
1159 return write_status;
1160 } else {
1161 formatter.AddField("write_status", "success");
1162 auto save_status = SaveTrackCollisionRom(rom, parser, formatter);
1163 if (!save_status.ok()) {
1164 formatter.EndObject();
1165 return save_status;
1166 }
1167 transaction->Commit();
1168 }
1169 }
1170
1171 formatter.EndObject();
1172 }
1173
1174 return absl::OkStatus();
1175}
1176
1177} // namespace handlers
1178} // namespace cli
1179} // 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 SaveToFile(const SaveSettings &settings)
Definition rom.cc:371
auto data() const
Definition rom.h:151
auto size() const
Definition rom.h:150
absl::StatusOr< uint32_t > ReadLong(int offset) const
Definition rom.cc:533
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.
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.
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.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
Loads and queries the hack manifest JSON for yaze-ASM integration.
const DungeonStreamLayout * GetDungeonStreamLayout(DungeonStreamType stream) const
Get an explicitly declared dungeon stream layout.
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
std::vector< WriteConflict > AnalyzePcWriteRanges(const std::vector< std::pair< uint32_t, uint32_t > > &pc_ranges) const
Analyze a set of PC-offset ranges for write conflicts.
absl::StatusOr< Room > GetRoom(int room_id)
static absl::StatusOr< DungeonSpawnPoint > Load(const Rom &rom, int spawn_id)
absl::Status ImportOracleSpriteRegistry(const std::string &csv_content)
Import sprite labels from Oracle of Secrets registry.csv format.
Dungeon Room Entrance or Spawn Point.
static int DetermineObjectType(uint8_t b1, uint8_t b3)
void LoadChests()
Definition room.cc:2545
const std::vector< chest_data > & GetChests() const
Definition room.h:260
uint8_t blockset() const
Definition room.h:902
void set_floor2(uint8_t value)
Definition room.h:935
void set_floor1(uint8_t value)
Definition room.h:928
void SetTag2(TagKey tag2)
Definition room.h:717
const std::vector< Door > & GetDoors() const
Definition room.h:350
void SetLayoutId(uint8_t id)
Definition room.h:917
void SetHolewarp(uint8_t hw)
Definition room.h:730
TagKey tag2() const
Definition room.h:889
uint8_t floor2() const
Definition room.h:927
const std::vector< staircase > & GetStairs() const
Definition room.h:267
uint8_t palette() const
Definition room.h:905
absl::Status SaveRoomHeader()
Definition room.cc:2281
TagKey tag1() const
Definition room.h:888
void SetEffect(EffectKey effect)
Definition room.h:703
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
Definition room.cc:2110
uint8_t spriteset() const
Definition room.h:904
void SetTag1(TagKey tag1)
Definition room.h:710
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
void LoadObjects()
Definition room.cc:1623
EffectKey effect() const
Definition room.h:887
void SetSpriteset(uint8_t ss)
Definition room.h:689
uint8_t floor1() const
Definition room.h:926
void SetBlockset(uint8_t bs)
Definition room.h:681
void LoadSprites()
Definition room.cc:2485
void SetPalette(uint8_t pal)
Definition room.h:674
uint8_t layout_id() const
Definition room.h:912
int id() const
Definition room.h:899
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
absl::Status MaybeLoadSpriteRegistry(const resources::ArgumentParser &parser)
absl::StatusOr< std::optional< RoomPropertyManifestContext > > LoadRoomPropertyManifestContext(const resources::ArgumentParser &parser)
bool IsEncodedRoomStreamObject(const zelda3::RoomObject &object)
absl::Status SaveTrackCollisionRom(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter)
absl::Status ValidateRoomPropertyManifestConflicts(const Rom &rom, int room_id, const RoomPropertyManifestContext &context)
void MergeStopTiles(const zelda3::CustomCollisionMap &existing, zelda3::CustomCollisionMap &generated)
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
absl::StatusOr< zelda3::DungeonStreamLayout > ToDungeonStreamAllocatorLayout(DungeonStreamType stream_type, const DungeonStreamLayout &manifest_layout)
constexpr int kDoorPointers
absl::Status WriteTrackCollision(Rom *rom, int room_id, const CustomCollisionMap &map)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:543
std::string GetRoomLabel(int id)
Convenience function to get a room label.
std::string GetItemLabel(int id)
Convenience function to get an item label.
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:518
constexpr int kNumDungeonSpawnPoints
constexpr int kNumberOfRooms
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
const char * ResolveSpriteName(uint16_t id)
Definition sprite.cc:284
absl::StatusOr< TrackCollisionResult > GenerateTrackCollision(Room *room, const GeneratorOptions &options)
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
constexpr int kRoomObjectPointer
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Treasure chest.
Definition zelda.h:425
std::array< uint8_t, 64 *64 > tiles
std::vector< DungeonStreamPcRange > allocation_ranges
std::vector< std::pair< int, int > > switch_promotions