yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_connection_edit.cc
Go to the documentation of this file.
2
3#include <optional>
4
5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
7#include "util/macro.h"
9
10namespace yaze::editor {
11namespace {
12
16
17bool IsOrdinaryDoor(DoorType type) {
18 return type == DoorType::NormalDoor || type == DoorType::NormalDoorLower;
19}
20
21bool IsOuterDoor(const Door& door) {
22 if (door.position >= 12) {
23 return false;
24 }
25 switch (door.direction) {
26 case DoorDirection::North:
27 case DoorDirection::West:
28 return door.position < 6;
29 case DoorDirection::South:
30 case DoorDirection::East:
31 return door.position >= 6;
32 }
33 return false;
34}
35
36bool SharesOuterLane(const Door& first, const Door& second) {
37 // Door variants 0/3 (and 6/9) occupy the same passage at different wall
38 // depths. A marker there can affect the other variant's corridor too:
39 // Underworld_LoadSingleDoorAttribute ($01BE35), especially $01BED1..BF17.
40 return first.direction == second.direction && IsOuterDoor(first) &&
41 IsOuterDoor(second) && first.position % 3 == second.position % 3;
42}
43
44Door ReturnDoor(const Door& source, DoorType type) {
45 // USDASM RoomDraw_DoorPartnerSelfLocation ($009AA2) and
46 // RoomDraw_DoorPartnerLocation ($009AD2) match N/W positions 0..5 to
47 // S/E positions 6..11. The internal seam slots are absent from these tables.
48 const auto direction =
49 static_cast<DoorDirection>(static_cast<unsigned>(source.direction) ^ 1);
50 const uint8_t position =
51 source.position < 6 ? source.position + 6 : source.position - 6;
52 const auto [byte1, byte2] =
53 zelda3::DoorPositionManager::EncodeDoorBytes(position, type, direction);
54 return Door::FromRomBytes(byte1, byte2);
55}
56
57void SetLayer(Door& door, DoorType type) {
58 if (door.type != type) {
59 door.type = type;
60 door.byte2 = static_cast<uint8_t>(type);
61 }
62}
63
64} // namespace
65
66bool SameDungeonDoors(const std::vector<Door>& first,
67 const std::vector<Door>& second) {
68 if (first.size() != second.size()) {
69 return false;
70 }
71 for (size_t i = 0; i < first.size(); ++i) {
72 const auto& a = first[i];
73 const auto& b = second[i];
74 if (a.position != b.position || a.type != b.type ||
75 a.direction != b.direction || a.byte1 != b.byte1 ||
76 a.byte2 != b.byte2) {
77 return false;
78 }
79 }
80 return true;
81}
82
87
88absl::StatusOr<int> DungeonConnectionTargetRoom(int source_room_id,
89 const Door& door) {
90 if (source_room_id < 0 || source_room_id >= zelda3::kNumberOfRooms) {
91 return absl::InvalidArgumentError("Source room is outside the room table");
92 }
93 if (static_cast<unsigned>(door.direction) > 3 || door.position >= 12) {
94 return absl::InvalidArgumentError("Door has an invalid direction or slot");
95 }
96 if (!IsOrdinaryDoor(door.type)) {
97 return absl::FailedPreconditionError(
98 "Reciprocal authoring supports normal upper or lower doors only");
99 }
100 if (!IsOuterDoor(door)) {
101 return absl::FailedPreconditionError(
102 "This door is on an internal room seam, not an adjacent-room wall");
103 }
104
105 // USDASM HandleEdgeTransitionMovement{East,West,South,North} changes the
106 // eight-bit $A0 at $02B68A/$02B72B/$02B7D7/$02B878; $A1 is unchanged.
107 // Never infer a connection across a row or page boundary from linear ids.
108 const int column = source_room_id & 0x0F;
109 const int row = (source_room_id & 0xFF) >> 4;
110 int target_room_id = -1;
111 switch (door.direction) {
112 case DoorDirection::North:
113 if (row > 0) {
114 target_room_id = source_room_id - 16;
115 }
116 break;
117 case DoorDirection::South:
118 if (row < 15) {
119 target_room_id = source_room_id + 16;
120 }
121 break;
122 case DoorDirection::West:
123 if (column > 0) {
124 target_room_id = source_room_id - 1;
125 }
126 break;
127 case DoorDirection::East:
128 if (column < 15) {
129 target_room_id = source_room_id + 1;
130 }
131 break;
132 }
133 if (target_room_id < 0 || target_room_id >= zelda3::kNumberOfRooms) {
134 return absl::FailedPreconditionError(
135 "No adjacent room exists at this room-table boundary");
136 }
137 return target_room_id;
138}
139
140absl::StatusOr<DungeonConnectionPlan> PlanDungeonDoorConnection(
141 const zelda3::Room& source, const zelda3::Room& target,
142 const DungeonConnectionRequest& request) {
143 if (request.source_room_id != source.id()) {
144 return absl::InvalidArgumentError("Selected source room changed");
145 }
146 if (request.layer != DungeonConnectionLayer::kUpper &&
148 return absl::InvalidArgumentError("Invalid connection layer");
149 }
150 const auto& source_doors = source.GetDoors();
151 if (request.source_door_index >= source_doors.size()) {
152 return absl::InvalidArgumentError("Selected source door no longer exists");
153 }
154 const auto& source_door = source_doors[request.source_door_index];
155 ASSIGN_OR_RETURN(const int target_room_id,
156 DungeonConnectionTargetRoom(source.id(), source_door));
157 if (target.id() != target_room_id || source.rom() != target.rom()) {
158 return absl::InvalidArgumentError(
159 "Target must be the adjacent room in the same ROM");
160 }
161 for (size_t i = 0; i < source_doors.size(); ++i) {
162 if (i != request.source_door_index &&
163 SharesOuterLane(source_door, source_doors[i])) {
164 return absl::FailedPreconditionError(
165 "Source passage contains another door or control marker");
166 }
167 }
168
169 const auto type = request.layer == DungeonConnectionLayer::kUpper
170 ? DoorType::NormalDoor
171 : DoorType::NormalDoorLower;
172 const Door return_door = ReturnDoor(source_door, type);
173 const auto& target_doors = target.GetDoors();
174 std::optional<size_t> target_index;
175 for (size_t i = 0; i < target_doors.size(); ++i) {
176 if (!SharesOuterLane(return_door, target_doors[i])) {
177 continue;
178 }
179 if (target_index || target_doors[i].position != return_door.position ||
180 !IsOrdinaryDoor(target_doors[i].type)) {
181 return absl::FailedPreconditionError(
182 "Return passage contains conflicting doors or control markers");
183 }
184 target_index = i;
185 }
186 const bool creates_return = !target_index.has_value();
187 if (creates_return &&
188 target_doors.size() >= static_cast<size_t>(zelda3::GetDungeonLimitMax(
190 return absl::ResourceExhaustedError(absl::StrFormat(
191 "Room 0x%03X has no free door slot (maximum %d)", target_room_id,
193 }
194
196 plan.request = request;
197 plan.rom = source.rom();
198 plan.target_room_id = target_room_id;
199 plan.target_door_index = target_index.value_or(target_doors.size());
200 plan.source_before = source_doors;
201 plan.source_after = source_doors;
202 plan.target_before = target_doors;
203 plan.target_after = target_doors;
204 plan.creates_return = creates_return;
205 SetLayer(plan.source_after[request.source_door_index], type);
206 if (creates_return) {
207 plan.target_after.push_back(return_door);
208 } else {
209 SetLayer(plan.target_after[*target_index], type);
210 }
211 return plan;
212}
213
214} // namespace yaze::editor
static std::pair< uint8_t, uint8_t > EncodeDoorBytes(uint8_t position, DoorType type, DoorDirection direction)
Encode door data for ROM storage.
const std::vector< Door > & GetDoors() const
Definition room.h:413
auto rom() const
Definition room.h:1082
int id() const
Definition room.h:1012
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
Editors are the view controllers for the application.
bool SameDungeonDoors(const std::vector< Door > &first, const std::vector< Door > &second)
absl::StatusOr< int > DungeonConnectionTargetRoom(int source_room_id, const Door &door)
absl::StatusOr< DungeonConnectionPlan > PlanDungeonDoorConnection(const zelda3::Room &source, const zelda3::Room &target, const DungeonConnectionRequest &request)
DoorType
Door types from ALTTP.
Definition door_types.h:33
constexpr int GetDungeonLimitMax(DungeonLimit limit)
constexpr int kNumberOfRooms
DoorDirection
Door direction on room walls.
Definition door_types.h:18
std::vector< zelda3::Room::Door > target_after
std::vector< zelda3::Room::Door > target_before
std::vector< zelda3::Room::Door > source_before
std::vector< zelda3::Room::Door > source_after
Represents a door in a dungeon room.
Definition room.h:339
DoorType type
Door type (determines appearance/behavior)
Definition room.h:341
DoorDirection direction
Which wall the door is on.
Definition room.h:342
uint8_t position
Encoded position (5-bit, 0-31)
Definition room.h:340
uint8_t byte2
Original ROM byte 2 (type + direction)
Definition room.h:345