yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_spawn_point.h
Go to the documentation of this file.
1#ifndef YAZE_ZELDA3_DUNGEON_DUNGEON_SPAWN_POINT_H
2#define YAZE_ZELDA3_DUNGEON_DUNGEON_SPAWN_POINT_H
3
4#include <array>
5#include <cstddef>
6#include <cstdint>
7#include <utility>
8#include <vector>
9
10#include "absl/status/status.h"
11#include "absl/status/statusor.h"
12#include "absl/strings/str_format.h"
13#include "rom/rom.h"
14#include "util/macro.h"
15
16namespace yaze::zelda3 {
17
18// Underworld_LoadSpawnEntrance reads seven records from these discontiguous
19// tables. The names mirror usdasm's SpawnPointData labels. In particular,
20// 0x15C2B is only the packed quadrant byte and 0x15C32 is a full 16-bit
21// overworld door tilemap value; neither is a regular-entrance door/exit field.
22inline constexpr uint32_t kDungeonSpawnRoom = 0x15B6E;
23inline constexpr uint32_t kDungeonSpawnCameraScrollBoundaries = 0x15B7C;
24inline constexpr uint32_t kDungeonSpawnHorizontalScroll = 0x15BB4;
25inline constexpr uint32_t kDungeonSpawnVerticalScroll = 0x15BC2;
26inline constexpr uint32_t kDungeonSpawnYCoordinate = 0x15BD0;
27inline constexpr uint32_t kDungeonSpawnXCoordinate = 0x15BDE;
28inline constexpr uint32_t kDungeonSpawnCameraTriggerY = 0x15BEC;
29inline constexpr uint32_t kDungeonSpawnCameraTriggerX = 0x15BFA;
30inline constexpr uint32_t kDungeonSpawnMainGfx = 0x15C08;
31inline constexpr uint32_t kDungeonSpawnFloor = 0x15C0F;
32inline constexpr uint32_t kDungeonSpawnDungeonId = 0x15C16;
33inline constexpr uint32_t kDungeonSpawnLayer = 0x15C1D;
34inline constexpr uint32_t kDungeonSpawnCameraScrollController = 0x15C24;
35inline constexpr uint32_t kDungeonSpawnQuadrant = 0x15C2B;
36inline constexpr uint32_t kDungeonSpawnOverworldDoorTilemap = 0x15C32;
37inline constexpr uint32_t kDungeonSpawnEntranceId = 0x15C40;
38inline constexpr uint32_t kDungeonSpawnSong = 0x15C4E;
39
40inline constexpr int kNumDungeonSpawnPoints = 0x07;
41inline constexpr int kNumRegularDungeonEntrances = 0x85;
42inline constexpr int kNumDungeonEntranceSlots =
44inline constexpr uint16_t kDungeonSpawnRoomIdMask = 0x01FF;
45
46// Compatibility names retained for existing read-only callers. New spawn code
47// must use the dedicated names above. kStartingEntranceDoor is intentionally a
48// documented alias only; DungeonSpawnPoint never treats that byte as a door.
50inline constexpr int kStartingEntranceScrollEdge =
56inline constexpr int kStartingEntranceCameraYTrigger =
58inline constexpr int kStartingEntranceCameraXTrigger =
64inline constexpr int kStartingEntrancescrolling =
72
73using DungeonSpawnPointWriteRange = std::pair<uint32_t, uint32_t>;
74
75// One spawn point occupies 17 discontiguous records totaling 33 bytes: nine
76// words, seven scalar bytes, and one eight-byte camera-boundary record.
77inline constexpr std::array<std::pair<uint32_t, uint32_t>, 17>
97
98inline std::vector<DungeonSpawnPointWriteRange> DungeonSpawnPointWriteRanges(
99 int spawn_id) {
100 if (spawn_id < 0 || spawn_id >= kNumDungeonSpawnPoints) {
101 return {};
102 }
103
104 std::vector<DungeonSpawnPointWriteRange> ranges;
105 ranges.reserve(kDungeonSpawnPointTableLayout.size());
106 for (const auto& [table_pc, record_width] : kDungeonSpawnPointTableLayout) {
107 const uint32_t begin =
108 table_pc + static_cast<uint32_t>(spawn_id) * record_width;
109 ranges.emplace_back(begin, begin + record_width);
110 }
111 return ranges;
112}
113
114class DungeonSpawnPoint;
115
116absl::Status ValidateDungeonSpawnPointForSave(const Rom& rom,
117 const DungeonSpawnPoint& spawn,
118 int spawn_id);
119
121 public:
122 DungeonSpawnPoint() = default;
123
124 static absl::StatusOr<DungeonSpawnPoint> Load(const Rom& rom, int spawn_id) {
125 if (!rom.is_loaded()) {
126 return absl::FailedPreconditionError("ROM not loaded");
127 }
128 if (spawn_id < 0 || spawn_id >= kNumDungeonSpawnPoints) {
129 return absl::OutOfRangeError(
130 absl::StrFormat("Dungeon spawn point ID %d is outside [0, %d)",
132 }
133 for (const auto& [begin, end] : DungeonSpawnPointWriteRanges(spawn_id)) {
134 if (end > rom.size()) {
135 return absl::FailedPreconditionError(absl::StrFormat(
136 "Dungeon spawn point 0x%02X read range 0x%06X-0x%06X exceeds "
137 "ROM size 0x%X",
138 spawn_id, begin, end, static_cast<uint32_t>(rom.size())));
139 }
140 }
141
142 const auto read_word = [&rom](uint32_t pc) {
143 return static_cast<uint16_t>(
144 rom.data()[pc] | (static_cast<uint16_t>(rom.data()[pc + 1]) << 8));
145 };
146
147 DungeonSpawnPoint spawn;
148 spawn.spawn_id_ = spawn_id;
149 spawn.room_id = read_word(kDungeonSpawnRoom + spawn_id * 2);
150 for (size_t i = 0; i < spawn.camera_scroll_boundaries.size(); ++i) {
151 spawn.camera_scroll_boundaries[i] =
153 }
154 spawn.horizontal_scroll =
156 spawn.vertical_scroll =
157 read_word(kDungeonSpawnVerticalScroll + spawn_id * 2);
158 spawn.y_coordinate = read_word(kDungeonSpawnYCoordinate + spawn_id * 2);
159 spawn.x_coordinate = read_word(kDungeonSpawnXCoordinate + spawn_id * 2);
160 spawn.camera_trigger_y =
161 read_word(kDungeonSpawnCameraTriggerY + spawn_id * 2);
162 spawn.camera_trigger_x =
163 read_word(kDungeonSpawnCameraTriggerX + spawn_id * 2);
165 spawn.floor = rom.data()[kDungeonSpawnFloor + spawn_id];
167 spawn.layer = rom.data()[kDungeonSpawnLayer + spawn_id];
173 spawn.entrance_id = read_word(kDungeonSpawnEntranceId + spawn_id * 2);
174 spawn.song = rom.data()[kDungeonSpawnSong + spawn_id];
175 return spawn;
176 }
177
178 absl::Status Save(Rom* rom, int spawn_id);
179
180 int spawn_id() const { return spawn_id_; }
181 bool dirty() const { return dirty_; }
182 void MarkDirty() { dirty_ = true; }
183 void ClearDirty() { dirty_ = false; }
184
185 uint16_t room_id = 0;
186 std::array<uint8_t, 8> camera_scroll_boundaries{};
187 uint16_t horizontal_scroll = 0;
188 uint16_t vertical_scroll = 0;
189 uint16_t y_coordinate = 0;
190 uint16_t x_coordinate = 0;
191 uint16_t camera_trigger_y = 0;
192 uint16_t camera_trigger_x = 0;
193 uint8_t main_gfx = 0;
194 uint8_t floor = 0;
195 uint8_t dungeon_id = 0;
196 uint8_t layer = 0;
198 uint8_t quadrant = 0;
200 uint16_t entrance_id = 0;
201 uint8_t song = 0;
202
203 private:
204 int spawn_id_ = -1;
205 bool dirty_ = false;
206};
207
209 const Rom& rom, const DungeonSpawnPoint& spawn, int spawn_id) {
210 if (spawn_id < 0 || spawn_id >= kNumDungeonSpawnPoints) {
211 return absl::OutOfRangeError(
212 absl::StrFormat("Dungeon spawn point ID %d is outside [0, %d)",
213 spawn_id, kNumDungeonSpawnPoints));
214 }
215 if (spawn.spawn_id() != spawn_id) {
216 return absl::InvalidArgumentError(absl::StrFormat(
217 "Dungeon spawn point model ID %d does not match requested save ID %d",
218 spawn.spawn_id(), spawn_id));
219 }
220 if (spawn.room_id > kDungeonSpawnRoomIdMask) {
221 return absl::OutOfRangeError(absl::StrFormat(
222 "Dungeon spawn point 0x%02X room ID 0x%04X exceeds the 9-bit "
223 "runtime range",
224 spawn_id, spawn.room_id));
225 }
227 return absl::OutOfRangeError(absl::StrFormat(
228 "Dungeon spawn point 0x%02X entrance ID 0x%04X is outside [0, "
229 "0x%02X)",
230 spawn_id, spawn.entrance_id, kNumRegularDungeonEntrances));
231 }
232 for (const auto& [begin, end] : DungeonSpawnPointWriteRanges(spawn_id)) {
233 if (end > rom.size()) {
234 return absl::FailedPreconditionError(absl::StrFormat(
235 "Dungeon spawn point 0x%02X write range 0x%06X-0x%06X exceeds "
236 "ROM size 0x%X",
237 spawn_id, begin, end, static_cast<uint32_t>(rom.size())));
238 }
239 }
240 return absl::OkStatus();
241}
242
243inline absl::Status DungeonSpawnPoint::Save(Rom* rom, int spawn_id) {
244 if (rom == nullptr || !rom->is_loaded()) {
245 return absl::FailedPreconditionError("ROM not loaded");
246 }
248
250 for (size_t i = 0; i < camera_scroll_boundaries.size(); ++i) {
254 }
281
282 ClearDirty();
283 return absl::OkStatus();
284}
285
286inline std::vector<DungeonSpawnPointWriteRange>
288 const std::array<DungeonSpawnPoint, kNumDungeonSpawnPoints>& spawn_points) {
289 std::vector<DungeonSpawnPointWriteRange> ranges;
290 for (int spawn_id = 0; spawn_id < kNumDungeonSpawnPoints; ++spawn_id) {
291 if (!spawn_points[spawn_id].dirty()) {
292 continue;
293 }
294 auto spawn_ranges = DungeonSpawnPointWriteRanges(spawn_id);
295 ranges.insert(ranges.end(), spawn_ranges.begin(), spawn_ranges.end());
296 }
297 return ranges;
298}
299
301 const Rom& rom,
302 const std::array<DungeonSpawnPoint, kNumDungeonSpawnPoints>& spawn_points,
303 bool dirty_only = true) {
304 for (int spawn_id = 0; spawn_id < kNumDungeonSpawnPoints; ++spawn_id) {
305 const auto& spawn = spawn_points[spawn_id];
306 if (dirty_only && !spawn.dirty()) {
307 continue;
308 }
310 }
311 return absl::OkStatus();
312}
313
314inline absl::Status SaveAllDungeonSpawnPoints(
315 Rom* rom,
316 std::array<DungeonSpawnPoint, kNumDungeonSpawnPoints>& spawn_points,
317 bool dirty_only = true) {
318 if (rom == nullptr || !rom->is_loaded()) {
319 return absl::FailedPreconditionError("ROM not loaded");
320 }
321
322 // Validate every participating record before the first write so malformed
323 // data and truncated ROMs fail without partially updating another spawn.
325 ValidateDungeonSpawnPointsForSave(*rom, spawn_points, dirty_only));
326 for (int spawn_id = 0; spawn_id < kNumDungeonSpawnPoints; ++spawn_id) {
327 auto& spawn = spawn_points[spawn_id];
328 if (dirty_only && !spawn.dirty()) {
329 continue;
330 }
331 RETURN_IF_ERROR(spawn.Save(rom, spawn_id));
332 }
333 return absl::OkStatus();
334}
335
336} // namespace yaze::zelda3
337
338#endif // YAZE_ZELDA3_DUNGEON_DUNGEON_SPAWN_POINT_H
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 WriteByte(int addr, uint8_t value)
Definition rom.cc:586
auto data() const
Definition rom.h:151
auto size() const
Definition rom.h:150
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:628
bool is_loaded() const
Definition rom.h:144
std::array< uint8_t, 8 > camera_scroll_boundaries
absl::Status Save(Rom *rom, int spawn_id)
static absl::StatusOr< DungeonSpawnPoint > Load(const Rom &rom, int spawn_id)
Zelda 3 specific classes and functions.
constexpr uint32_t kDungeonSpawnCameraScrollBoundaries
constexpr uint32_t kDungeonSpawnMainGfx
constexpr uint32_t kDungeonSpawnVerticalScroll
constexpr int kStartingEntranceLadderBG
constexpr uint32_t kDungeonSpawnSong
constexpr int kStartingEntranceXScroll
constexpr std::array< std::pair< uint32_t, uint32_t >, 17 > kDungeonSpawnPointTableLayout
constexpr int kStartingEntranceentrance
constexpr uint32_t kDungeonSpawnRoom
std::pair< uint32_t, uint32_t > DungeonSpawnPointWriteRange
constexpr uint32_t kDungeonSpawnLayer
constexpr int kStartingEntranceScrollQuadrant
constexpr int kStartingEntrancescrolling
constexpr int kNumRegularDungeonEntrances
constexpr uint32_t kDungeonSpawnHorizontalScroll
absl::Status SaveAllDungeonSpawnPoints(Rom *rom, std::array< DungeonSpawnPoint, kNumDungeonSpawnPoints > &spawn_points, bool dirty_only=true)
constexpr int kStartingEntranceBlockset
constexpr uint32_t kDungeonSpawnCameraScrollController
constexpr uint32_t kDungeonSpawnCameraTriggerY
constexpr uint32_t kDungeonSpawnDungeonId
constexpr int kStartingEntrancemusic
constexpr int kStartingEntranceCameraYTrigger
constexpr uint32_t kDungeonSpawnQuadrant
constexpr int kStartingEntranceFloor
constexpr int kStartingEntranceYPosition
constexpr int kStartingEntranceXPosition
std::vector< DungeonSpawnPointWriteRange > DungeonSpawnPointWriteRanges(int spawn_id)
constexpr uint16_t kDungeonSpawnRoomIdMask
constexpr uint32_t kDungeonSpawnXCoordinate
constexpr int kStartingEntranceCameraXTrigger
constexpr int kStartingEntranceroom
constexpr uint32_t kDungeonSpawnCameraTriggerX
constexpr int kStartingEntranceScrollEdge
absl::Status ValidateDungeonSpawnPointForSave(const Rom &rom, const DungeonSpawnPoint &spawn, int spawn_id)
constexpr int kNumDungeonSpawnPoints
constexpr int kStartingEntranceDoor
constexpr uint32_t kDungeonSpawnOverworldDoorTilemap
constexpr int kStartingEntranceDungeon
absl::Status ValidateDungeonSpawnPointsForSave(const Rom &rom, const std::array< DungeonSpawnPoint, kNumDungeonSpawnPoints > &spawn_points, bool dirty_only=true)
constexpr uint32_t kDungeonSpawnYCoordinate
constexpr int kStartingEntranceexit
constexpr uint32_t kDungeonSpawnEntranceId
constexpr int kNumDungeonEntranceSlots
constexpr uint32_t kDungeonSpawnFloor
constexpr int kStartingEntranceYScroll
std::vector< DungeonSpawnPointWriteRange > CollectDirtyDungeonSpawnPointWriteRanges(const std::array< DungeonSpawnPoint, kNumDungeonSpawnPoints > &spawn_points)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22