yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_layout.cc
Go to the documentation of this file.
1#include "room_layout.h"
2
3#include "absl/strings/str_format.h"
4#include "rom/snes.h"
5#include "util/log.h"
8
9namespace yaze::zelda3 {
10
11namespace {
12
13// Returns true if the object ID is a pit or layer mask that should be on BG2.
14// These objects create transparency holes in BG1 to show BG2 content through.
15// Based on ALTTP object IDs and draw routine analysis.
16bool IsPitOrMaskObject(int16_t id) {
17 // Pit and layer-2 masks that rely on BG1 transparency to reveal BG2.
18 //
19 // Source of truth for names: RoomObjectNames in room_object.h.
20 // USDASM: These are "layer 2 mask" objects that operate by clearing BG1.
21 if (id == 0xA4)
22 return true; // Pit
23 if (id >= 0xA5 && id <= 0xAC)
24 return true; // Diagonal layer 2 masks
25 if (id == 0xC2 || id == 0xC3)
26 return true; // Layer 2 pit masks (large/medium)
27 if (id == 0xC6 || id == 0xD7 || id == 0xD9)
28 return true; // Layer 2 masks (large/medium/swim)
29 if (id == 0xFE6)
30 return true; // Type 3 pit
31 if (id == 0xFF3)
32 return true; // Type 3 layer 2 mask (full)
33
34 return false;
35}
36
37} // namespace
38
39absl::StatusOr<int> RoomLayout::GetLayoutAddress(int layout_id) const {
40 if (!rom_ || !rom_->is_loaded()) {
41 return absl::FailedPreconditionError("ROM not loaded");
42 }
43
44 if (layout_id < 0 ||
45 layout_id >= static_cast<int>(kRoomLayoutPointers.size())) {
46 return absl::InvalidArgumentError(
47 absl::StrFormat("Invalid layout id %d", layout_id));
48 }
49
50 uint32_t snes_addr = kRoomLayoutPointers[layout_id];
51 int pc_addr = SnesToPc(static_cast<int>(snes_addr));
52 if (pc_addr < 0 || pc_addr >= static_cast<int>(rom_->size())) {
53 return absl::OutOfRangeError(
54 absl::StrFormat("Layout pointer %d out of range", layout_id));
55 }
56 return pc_addr;
57}
58
59absl::Status RoomLayout::LoadLayout(int layout_id) {
60 objects_.clear();
61
62 auto addr_result = GetLayoutAddress(layout_id);
63 if (!addr_result.ok()) {
64 return addr_result.status();
65 }
66
67 int pos = addr_result.value();
68 const auto& rom_data = rom_->data();
69 int layer = 0;
70
71 LOG_DEBUG("RoomLayout", "Loading layout %d from PC address 0x%05X", layout_id,
72 pos);
73
74 int obj_index = 0;
75 while (pos + 2 < static_cast<int>(rom_->size())) {
76 uint8_t b1 = rom_data[pos];
77 uint8_t b2 = rom_data[pos + 1];
78
79 if (b1 == 0xFF && b2 == 0xFF) {
80 // $FF $FF is the END terminator for this layout
81 LOG_DEBUG("RoomLayout",
82 "Layout %d terminated at pos=0x%05X after %d objects",
83 layout_id, pos, obj_index);
84 break;
85 }
86
87 if (pos + 2 >= static_cast<int>(rom_->size())) {
88 break;
89 }
90
91 uint8_t b3 = rom_data[pos + 2];
92 pos += 3;
93
95 b1, b2, b3, static_cast<uint8_t>(layer));
96
97 // Pit/mask objects should be on BG2 layer to create transparency holes.
98 // This allows them to show through BG1 content (walls, floor).
99 if (IsPitOrMaskObject(obj.id_)) {
101 LOG_DEBUG("RoomLayout", "Pit/mask object 0x%03X assigned to BG2 layer",
102 obj.id_);
103 }
104
105 obj.SetRom(rom_);
106 obj.EnsureTilesLoaded();
107 objects_.push_back(obj);
108
109 LOG_DEBUG("RoomLayout",
110 "Layout %d obj[%d]: bytes=[%02X,%02X,%02X] -> id=0x%03X x=%d "
111 "y=%d size=%d tiles=%zu",
112 layout_id, obj_index, b1, b2, b3, obj.id_, obj.x_, obj.y_,
113 obj.size_, obj.tiles().size());
114 obj_index++;
115 }
116
117 LOG_DEBUG("RoomLayout", "Layout %d loaded with %zu objects", layout_id,
118 objects_.size());
119
120 return absl::OkStatus();
121}
122
123absl::Status RoomLayout::Draw(int room_id, const uint8_t* gfx_data,
126 const gfx::PaletteGroup& palette_group,
127 DungeonState* state) const {
128 if (!rom_ || !rom_->is_loaded()) {
129 return absl::FailedPreconditionError("ROM not loaded");
130 }
131
132 if (objects_.empty()) {
133 return absl::OkStatus();
134 }
135
136 ObjectDrawer drawer(rom_, room_id, gfx_data);
137 // Pass bg1 as layout_bg1 so BG2 objects (pits/masks) will mark the
138 // corresponding area in BG1 as transparent via MarkBG1Transparent().
139 // This creates "holes" in BG1 that allow BG2 content to show through.
140 return drawer.DrawObjectList(objects_, bg1, bg2, palette_group, state, &bg1);
141}
142
143} // namespace yaze::zelda3
auto data() const
Definition rom.h:139
auto size() const
Definition rom.h:138
bool is_loaded() const
Definition rom.h:132
Interface for accessing dungeon game state.
Draws dungeon objects to background buffers using game patterns.
absl::Status DrawObjectList(const std::vector< RoomObject > &objects, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr)
Draw all objects in a room.
std::vector< RoomObject > objects_
Definition room_layout.h:37
absl::Status Draw(int room_id, const uint8_t *gfx_data, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, DungeonState *state) const
absl::StatusOr< int > GetLayoutAddress(int layout_id) const
absl::Status LoadLayout(int layout_id)
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
const std::vector< gfx::TileInfo > & tiles() const
Definition room_object.h:91
void SetRom(Rom *rom)
Definition room_object.h:70
#define LOG_DEBUG(category, format,...)
Definition log.h:103
Zelda 3 specific classes and functions.
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
Represents a group of palettes.