6#include "absl/strings/str_format.h"
7#include "absl/strings/str_split.h"
40 for (
int i = 0; i < static_cast<int>(existing.
tiles.size()); ++i) {
49 auto registry_path = parser.
GetString(
"sprite-registry");
50 if (!registry_path.has_value()) {
51 return absl::OkStatus();
54 std::ifstream file(registry_path.value());
55 if (!file.is_open()) {
56 return absl::NotFoundError(absl::StrFormat(
57 "Could not open sprite registry: %s", registry_path.value()));
60 std::stringstream buffer;
61 buffer << file.rdbuf();
71 auto registry_status = MaybeLoadSpriteRegistry(parser);
72 if (!registry_status.ok()) {
73 return registry_status;
76 auto room_id_str = parser.
GetString(
"room").value();
79 if (!ParseHexString(room_id_str, &room_id)) {
80 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
84 formatter.
AddField(
"room_id", room_id);
90 formatter.
AddField(
"total_sprites",
static_cast<int>(sprites.size()));
91 formatter.
AddField(
"status",
"success");
94 for (
const auto& sprite : sprites) {
100 formatter.
AddField(
"subtype", sprite.subtype());
101 formatter.
AddField(
"layer", sprite.layer());
102 if (sprite.key_drop() > 0) {
103 formatter.
AddField(
"key_drop", sprite.key_drop());
110 return absl::OkStatus();
117 auto registry_status = MaybeLoadSpriteRegistry(parser);
118 if (!registry_status.ok()) {
119 return registry_status;
122 auto room_id_str = parser.
GetString(
"room").value();
125 if (!ParseHexString(room_id_str, &room_id)) {
126 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
129 formatter.
AddField(
"room_id", room_id);
134 formatter.
AddField(
"status",
"success");
135 formatter.
AddField(
"name", absl::StrFormat(
"Room %d", room.
id()));
137 formatter.
AddField(
"room_type",
"Dungeon Room");
148 formatter.
AddField(
"tag1",
static_cast<int>(room.
tag1()));
149 formatter.
AddField(
"tag2",
static_cast<int>(room.
tag2()));
159 for (
const auto& door : room.
GetDoors()) {
161 formatter.
AddField(
"position", door.position);
162 formatter.
AddField(
"direction", std::string(door.GetDirectionName()));
163 formatter.
AddField(
"type", std::string(door.GetTypeName()));
164 auto [tx, ty] = door.GetTileCoords();
173 for (
const auto& stair : room.
GetStairs()) {
178 "tile_y", stair.room);
179 formatter.
AddField(
"label", stair.label);
195 return absl::OkStatus();
201 auto room_id_opt = parser.
GetString(
"room");
203 bool has_room_filter = room_id_opt.has_value();
204 int room_filter = -1;
205 if (has_room_filter) {
206 if (!ParseHexString(room_id_opt.value(), &room_filter)) {
207 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
212 int rooms_with_chests = 0;
213 int total_chests = 0;
214 std::map<int, int> item_counts;
217 formatter.
AddField(
"room_filter", has_room_filter
218 ? absl::StrFormat(
"0x%02X", room_filter)
222 int start_room = has_room_filter ? room_filter : 0;
225 for (
int room_id = start_room; room_id <= end_room; ++room_id) {
231 if (chests.empty()) {
236 total_chests +=
static_cast<int>(chests.size());
239 formatter.
AddField(
"room_id", absl::StrFormat(
"0x%02X", room_id));
241 formatter.
AddField(
"chest_count",
static_cast<int>(chests.size()));
245 for (
const auto&
chest : chests) {
247 formatter.
AddField(
"index", chest_index++);
254 item_counts[
chest.id]++;
263 formatter.
AddField(
"total_rooms", total_rooms);
264 formatter.
AddField(
"rooms_with_chests", rooms_with_chests);
265 formatter.
AddField(
"total_chests", total_chests);
266 formatter.
AddField(
"unique_items",
static_cast<int>(item_counts.size()));
269 for (
const auto& [item_id, count] : item_counts) {
283 return absl::OkStatus();
289 auto entrance_id_str = parser.
GetString(
"entrance").value();
290 bool is_spawn_point = parser.
HasFlag(
"spawn");
293 if (!ParseHexString(entrance_id_str, &entrance_id)) {
294 return absl::InvalidArgumentError(
295 "Invalid entrance ID format. Must be hex.");
301 formatter.
AddField(
"entrance_id", absl::StrFormat(
"0x%02X", entrance_id));
302 formatter.
AddField(
"is_spawn_point", is_spawn_point);
303 formatter.
AddField(
"room_id", absl::StrFormat(
"0x%04X", entrance.
room_));
304 formatter.
AddField(
"exit_id", absl::StrFormat(
"0x%04X", entrance.
exit_));
320 formatter.
AddField(
"floor", absl::StrFormat(
"0x%02X", entrance.
floor_));
323 formatter.
AddField(
"door", absl::StrFormat(
"0x%02X", entrance.
door_));
325 absl::StrFormat(
"0x%02X", entrance.
ladder_bg_));
327 absl::StrFormat(
"0x%02X", entrance.
scrolling_));
328 formatter.
AddField(
"scroll_quadrant",
330 formatter.
AddField(
"music", absl::StrFormat(
"0x%02X", entrance.
music_));
352 return absl::OkStatus();
358 auto room_id_str = parser.
GetString(
"room").value();
361 if (!ParseHexString(room_id_str, &room_id)) {
362 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
366 formatter.
AddField(
"room_id", room_id);
370 auto room_or = dungeon_editor.
GetRoom(room_id);
372 formatter.
AddField(
"status",
"error");
373 formatter.
AddField(
"error", room_or.status().ToString());
375 return room_or.status();
378 auto& room = room_or.value();
381 formatter.
AddField(
"status",
"success");
382 formatter.
AddField(
"room_width",
"Unknown");
383 formatter.
AddField(
"room_height",
"Unknown");
384 formatter.
AddField(
"room_name", absl::StrFormat(
"Room %d", room.id()));
388 formatter.
AddField(
"tiles",
"Room tile data would be exported here");
389 formatter.
AddField(
"sprites",
"Room sprite data would be exported here");
390 formatter.
AddField(
"doors",
"Room door data would be exported here");
395 return absl::OkStatus();
401 auto room_id_str = parser.
GetString(
"room").value();
404 if (!ParseHexString(room_id_str, &room_id)) {
405 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
409 formatter.
AddField(
"room_id", room_id);
413 auto room_or = dungeon_editor.
GetRoom(room_id);
415 formatter.
AddField(
"status",
"error");
416 formatter.
AddField(
"error", room_or.status().ToString());
418 return room_or.status();
421 auto& room = room_or.value();
426 const auto& objects = room.GetTileObjects();
427 formatter.
AddField(
"total_objects",
static_cast<int>(objects.size()));
428 formatter.
AddField(
"status",
"success");
431 for (
const auto& obj : objects) {
434 formatter.
AddField(
"id_hex", absl::StrFormat(
"0x%04X", obj.id_));
437 formatter.
AddField(
"size", obj.size_);
438 formatter.
AddField(
"layer",
static_cast<int>(obj.layer_));
448 return absl::OkStatus();
454 auto room_id_str = parser.
GetString(
"room").value();
457 if (!ParseHexString(room_id_str, &room_id)) {
458 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
462 formatter.
AddField(
"room_id", room_id);
466 auto room_or = dungeon_editor.
GetRoom(room_id);
468 formatter.
AddField(
"status",
"error");
469 formatter.
AddField(
"error", room_or.status().ToString());
471 return room_or.status();
474 auto& room = room_or.value();
477 formatter.
AddField(
"room_width",
"Unknown");
478 formatter.
AddField(
"room_height",
"Unknown");
479 formatter.
AddField(
"total_tiles",
"Unknown");
480 formatter.
AddField(
"status",
"not_implemented");
482 "Tile data retrieval requires room tile parsing");
488 return absl::OkStatus();
494 auto room_id_str = parser.
GetString(
"room").value();
495 auto property = parser.
GetString(
"property").value();
496 auto value = parser.
GetString(
"value").value();
499 if (!ParseHexString(room_id_str, &room_id)) {
500 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
503 formatter.
BeginObject(
"Dungeon Room Property Set");
504 formatter.
AddField(
"room_id", room_id);
505 formatter.
AddField(
"property", property);
510 auto room_or = dungeon_editor.
GetRoom(room_id);
512 formatter.
AddField(
"status",
"error");
513 formatter.
AddField(
"error", room_or.status().ToString());
515 return room_or.status();
519 formatter.
AddField(
"status",
"not_implemented");
521 "Property setting requires room property system");
524 return absl::OkStatus();
530 auto room_id_str = parser.
GetString(
"room").value();
533 if (!ParseHexString(room_id_str, &room_id)) {
534 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
538 formatter.
AddField(
"room_id", room_id);
552 int header_pointer_pc =
SnesToPc(header_pointer);
555 formatter.
AddHexField(
"snes_address", header_pointer, 6);
556 formatter.
AddHexField(
"pc_address", header_pointer_pc, 6);
560 int table_offset = header_pointer_pc + (room_id * 2);
562 (rom->
data()[table_offset + 1] << 8) +
563 rom->
data()[table_offset];
564 int room_header_pc =
SnesToPc(room_header_addr);
567 formatter.
AddHexField(
"table_offset_pc", table_offset, 6);
568 formatter.
AddHexField(
"snes_address", room_header_addr, 6);
569 formatter.
AddHexField(
"pc_address", room_header_pc, 6);
574 for (
int i = 0; i < 14; ++i) {
575 if (room_header_pc + i <
static_cast<int>(rom->
size())) {
577 absl::StrFormat(
"0x%02X", rom->
data()[room_header_pc + i]));
583 if (room_header_pc >= 0 &&
584 room_header_pc + 13 <
static_cast<int>(rom->
size())) {
585 uint8_t byte0 = rom->
data()[room_header_pc];
586 uint8_t byte1 = rom->
data()[room_header_pc + 1];
587 uint8_t byte2 = rom->
data()[room_header_pc + 2];
588 uint8_t byte3 = rom->
data()[room_header_pc + 3];
591 formatter.
AddField(
"bg2", (byte0 >> 5) & 0x07);
592 formatter.
AddField(
"collision", (byte0 >> 2) & 0x07);
593 formatter.
AddField(
"is_light", (byte0 & 0x01) == 1);
594 formatter.
AddField(
"palette", byte1 & 0x3F);
595 formatter.
AddField(
"blockset", byte2);
596 formatter.
AddField(
"spriteset", byte3);
597 formatter.
AddField(
"effect", rom->
data()[room_header_pc + 4]);
598 formatter.
AddField(
"tag1", rom->
data()[room_header_pc + 5]);
599 formatter.
AddField(
"tag2", rom->
data()[room_header_pc + 6]);
600 formatter.
AddField(
"holewarp", rom->
data()[room_header_pc + 9]);
601 formatter.
AddField(
"stair1_room", rom->
data()[room_header_pc + 10]);
602 formatter.
AddField(
"stair2_room", rom->
data()[room_header_pc + 11]);
603 formatter.
AddField(
"stair3_room", rom->
data()[room_header_pc + 12]);
604 formatter.
AddField(
"stair4_room", rom->
data()[room_header_pc + 13]);
607 formatter.
AddField(
"error",
"Room header address out of range");
611 return absl::OkStatus();
621 auto switch_str = parser.
GetString(
"promote-switch");
622 if (switch_str.has_value()) {
623 for (absl::string_view pair :
624 absl::StrSplit(switch_str.value(),
' ', absl::SkipEmpty())) {
625 std::vector<std::string> coords =
626 absl::StrSplit(pair,
',', absl::SkipEmpty());
627 if (coords.size() == 2) {
629 if (ParseHexString(coords[0], &sx) && ParseHexString(coords[1], &sy)) {
636 bool do_write = parser.
HasFlag(
"write");
637 bool do_preserve_stops = parser.
HasFlag(
"preserve-stops");
638 bool do_visualize = parser.
HasFlag(
"visualize");
641 std::vector<int> room_ids;
642 auto rooms_arg = parser.
GetString(
"rooms");
643 auto room_arg = parser.
GetString(
"room");
645 if (rooms_arg.has_value()) {
647 for (absl::string_view token :
648 absl::StrSplit(rooms_arg.value(),
',', absl::SkipEmpty())) {
650 std::string token_str(token);
651 if (!ParseHexString(token_str, &rid)) {
652 return absl::InvalidArgumentError(absl::StrFormat(
653 "Invalid room ID '%s' in --rooms list. Must be hex.", token_str));
655 room_ids.push_back(rid);
657 if (room_ids.empty()) {
658 return absl::InvalidArgumentError(
"--rooms list is empty.");
660 }
else if (room_arg.has_value()) {
663 if (!ParseHexString(room_arg.value(), &rid)) {
664 return absl::InvalidArgumentError(
"Invalid room ID format. Must be hex.");
666 room_ids.push_back(rid);
668 return absl::InvalidArgumentError(
"Either --room or --rooms is required.");
671 bool is_batch = room_ids.size() > 1;
675 formatter.
BeginObject(
"Batch Track Collision Generation");
676 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
677 formatter.
AddField(
"room_count",
static_cast<int>(room_ids.size()));
681 int total_corners = 0;
682 int total_switches = 0;
683 int rooms_succeeded = 0;
686 for (
int room_id : room_ids) {
695 formatter.
AddField(
"error", std::string(result.status().message()));
697 return absl::InternalError(
698 absl::StrFormat(
"Generation failed for room 0x%03X: %s", room_id,
699 result.status().message()));
702 if (do_preserve_stops && do_write) {
704 if (existing.ok() && existing->has_data) {
705 MergeStopTiles(*existing, result->collision_map);
711 formatter.
AddField(
"tiles_generated", result->tiles_generated);
712 formatter.
AddField(
"stop_count", result->stop_count);
713 formatter.
AddField(
"corner_count", result->corner_count);
714 formatter.
AddField(
"switch_count", result->switch_count);
715 if (do_preserve_stops) {
716 formatter.
AddField(
"stops_preserved",
true);
720 formatter.
AddField(
"visualization", result->ascii_visualization);
726 if (!write_status.ok()) {
729 std::string(write_status.message()));
733 return absl::InternalError(
734 absl::StrFormat(
"Write failed for room 0x%03X: %s", room_id,
735 write_status.message()));
737 formatter.
AddField(
"write_status",
"success");
740 total_tiles += result->tiles_generated;
741 total_stops += result->stop_count;
742 total_corners += result->corner_count;
743 total_switches += result->switch_count;
753 save_settings.
backup =
true;
754 auto save_status = rom->
SaveToFile(save_settings);
755 if (!save_status.ok()) {
756 formatter.
AddField(
"save_error", std::string(save_status.message()));
758 formatter.
AddField(
"save_status",
"saved");
764 formatter.
AddField(
"rooms_succeeded", rooms_succeeded);
765 formatter.
AddField(
"tiles_generated", total_tiles);
766 formatter.
AddField(
"stop_count", total_stops);
767 formatter.
AddField(
"corner_count", total_corners);
768 formatter.
AddField(
"switch_count", total_switches);
774 int room_id = room_ids[0];
781 return result.status();
784 if (do_preserve_stops && do_write) {
786 if (existing.ok() && existing->has_data) {
787 MergeStopTiles(*existing, result->collision_map);
791 formatter.
BeginObject(
"Track Collision Generation");
793 formatter.
AddField(
"tiles_generated", result->tiles_generated);
794 formatter.
AddField(
"stop_count", result->stop_count);
795 formatter.
AddField(
"corner_count", result->corner_count);
796 formatter.
AddField(
"switch_count", result->switch_count);
797 formatter.
AddField(
"mode", do_write ?
"write" :
"dry-run");
798 if (do_preserve_stops) {
799 formatter.
AddField(
"stops_preserved",
true);
802 if (do_visualize || !do_write) {
803 formatter.
AddField(
"visualization", result->ascii_visualization);
809 if (!write_status.ok()) {
810 formatter.
AddField(
"write_error", std::string(write_status.message()));
812 formatter.
AddField(
"write_status",
"success");
815 save_settings.
backup =
true;
816 auto save_status = rom->
SaveToFile(save_settings);
817 if (!save_status.ok()) {
818 formatter.
AddField(
"save_error", std::string(save_status.message()));
820 formatter.
AddField(
"save_status",
"saved");
828 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status SaveToFile(const SaveSettings &settings)
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.
Core dungeon editing system.
absl::StatusOr< Room > GetRoom(int room_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.
uint8_t camera_boundary_fw_
uint8_t camera_boundary_qe_
uint8_t camera_boundary_fe_
uint8_t camera_boundary_fs_
uint8_t camera_boundary_qs_
uint8_t camera_boundary_qw_
uint16_t camera_trigger_y_
uint8_t camera_boundary_fn_
uint16_t camera_trigger_x_
uint8_t camera_boundary_qn_
static int DetermineObjectType(uint8_t b1, uint8_t b3)
const std::vector< chest_data > & GetChests() const
const std::vector< Door > & GetDoors() const
const std::vector< staircase > & GetStairs() const
uint8_t spriteset() const
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< RoomObject > & GetTileObjects() const
uint8_t layout_id() const
absl::Status MaybeLoadSpriteRegistry(const resources::ArgumentParser &parser)
bool IsStopTile(uint8_t v)
constexpr uint8_t kStopTileMin
constexpr uint8_t kStopTileMax
void MergeStopTiles(const zelda3::CustomCollisionMap &existing, zelda3::CustomCollisionMap &generated)
bool ParseHexString(absl::string_view str, int *out)
absl::Status WriteTrackCollision(Rom *rom, int room_id, const CustomCollisionMap &map)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
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)
constexpr int kNumberOfRooms
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
const char * ResolveSpriteName(uint16_t id)
absl::StatusOr< TrackCollisionResult > GenerateTrackCollision(Room *room, const GeneratorOptions &options)
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
uint32_t SnesToPc(uint32_t addr) noexcept
std::array< uint8_t, 64 *64 > tiles
std::vector< std::pair< int, int > > switch_promotions