yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
chest_edit.cc
Go to the documentation of this file.
2
3#include <cstdint>
4
5#include "absl/strings/str_format.h"
8
9namespace yaze::zelda3 {
10namespace {
11
12bool IsChest(const RoomObject& object) {
13 return UsesRoomObjectStream(object) && IsStatefulChestObjectId(object.id_);
14}
15
16std::vector<size_t> StreamObjectIndices(const std::vector<RoomObject>& objects,
17 bool include_locks = false) {
18 std::vector<size_t> indices;
19 for (uint8_t list = 0; list < 3; ++list) {
20 for (size_t index = 0; index < objects.size(); ++index) {
21 const auto& object = objects[index];
22 if (object.GetLayerValue() == list && UsesRoomObjectStream(object) &&
23 (IsStatefulChestObjectId(object.id_) ||
24 (include_locks && object.id_ == 0xF98))) {
25 indices.push_back(index);
26 }
27 }
28 }
29 return indices;
30}
31
32absl::Status ValidateRoomEventSlots(const std::vector<RoomObject>& objects) {
33 size_t slots = 0;
34 bool saw_lock = false;
35 for (uint8_t list = 0; list < 3; ++list) {
36 for (const auto& object : objects) {
37 if (!UsesRoomObjectStream(object) || object.GetLayerValue() != list) {
38 continue;
39 }
40 if (object.id_ == 0xF98) {
41 saw_lock = true;
42 ++slots;
43 } else if (IsStatefulChestObjectId(object.id_)) {
44 if (saw_lock) {
45 return absl::FailedPreconditionError(
46 "Place stateful chests before big-key locks in object-list "
47 "order to avoid shared room-state slots");
48 }
49 ++slots;
50 }
51 }
52 }
53 if (slots > kMaxChests) {
54 return absl::ResourceExhaustedError(
55 "A room supports at most six stateful chests and big-key locks "
56 "combined");
57 }
58 return absl::OkStatus();
59}
60
61} // namespace
62
63std::optional<size_t> ChestIndexForObject(
64 const std::vector<RoomObject>& objects, size_t object_index) {
65 const auto indices = StreamObjectIndices(objects);
66 for (size_t ordinal = 0; ordinal < indices.size(); ++ordinal) {
67 if (indices[ordinal] == object_index) {
68 return ordinal;
69 }
70 }
71 return std::nullopt;
72}
73
74absl::Status ValidateChestObjectMapping(const std::vector<RoomObject>& objects,
75 const std::vector<chest_data>& chests) {
76 const auto indices = StreamObjectIndices(objects);
77 if (chests.size() != indices.size()) {
78 return absl::FailedPreconditionError(absl::StrFormat(
79 "Room has %zu stateful chest objects but %zu contents records; "
80 "resolve the mapping before adding, deleting, or reordering chests",
81 indices.size(), chests.size()));
82 }
83 for (size_t ordinal = 0; ordinal < indices.size(); ++ordinal) {
84 if (chests[ordinal].size != (objects[indices[ordinal]].id_ == 0xFB1)) {
85 return absl::FailedPreconditionError(absl::StrFormat(
86 "Chest %zu contents type does not match its small/big object; "
87 "resolve the mapping before changing chest objects",
88 ordinal + 1));
89 }
90 }
91 return absl::OkStatus();
92}
93
94absl::StatusOr<std::vector<chest_data>> PlanChestObjectEdit(
95 const std::vector<RoomObject>& before_objects,
96 const std::vector<chest_data>& before_chests,
97 const std::vector<RoomObject>& after_objects,
98 const std::vector<std::optional<size_t>>& source_indices,
99 const std::vector<std::optional<chest_data>>& chest_overrides) {
100 if (source_indices.size() != after_objects.size()) {
101 return absl::InvalidArgumentError(
102 "Chest edit source indices must match the candidate objects");
103 }
104 if (!chest_overrides.empty() &&
105 chest_overrides.size() != after_objects.size()) {
106 return absl::InvalidArgumentError(
107 "Chest overrides must match the candidate objects");
108 }
109 for (size_t index = 0; index < after_objects.size(); ++index) {
110 if (source_indices[index] &&
111 *source_indices[index] >= before_objects.size()) {
112 return absl::InvalidArgumentError("Chest edit source index is invalid");
113 }
114 const auto& object = after_objects[index];
115 if (UsesRoomObjectStream(object) &&
116 (IsChest(object) || object.id_ == 0xF98) &&
117 object.GetLayerValue() > 2) {
118 return absl::InvalidArgumentError(
119 "Chest or big-key lock object-list index must be in range 0..2");
120 }
121 if (!chest_overrides.empty() && chest_overrides[index]) {
122 if (!IsChest(object)) {
123 return absl::InvalidArgumentError(
124 "Only stateful chest objects can have contents overrides");
125 }
126 if (chest_overrides[index]->size != (object.id_ == 0xFB1)) {
127 return absl::InvalidArgumentError(
128 "Chest contents override must match the small/big object type");
129 }
130 }
131 }
132
133 const auto before_indices = StreamObjectIndices(before_objects);
134 const auto after_indices = StreamObjectIndices(after_objects);
135 const auto before_events = StreamObjectIndices(before_objects, true);
136 const auto after_events = StreamObjectIndices(after_objects, true);
137 bool unchanged = before_events.size() == after_events.size();
138 for (size_t ordinal = 0; unchanged && ordinal < after_events.size();
139 ++ordinal) {
140 const size_t index = after_events[ordinal];
141 unchanged =
142 source_indices[index] == before_events[ordinal] &&
143 after_objects[index].id_ == before_objects[before_events[ordinal]].id_;
144 }
145 for (size_t ordinal = 0; unchanged && ordinal < after_indices.size();
146 ++ordinal) {
147 const size_t index = after_indices[ordinal];
148 if (!chest_overrides.empty() && chest_overrides[index]) {
149 const auto& replacement = *chest_overrides[index];
150 unchanged = unchanged && ordinal < before_chests.size() &&
151 replacement.id == before_chests[ordinal].id &&
152 replacement.size == before_chests[ordinal].size;
153 }
154 }
155 if (unchanged) {
156 return before_chests;
157 }
158
159 const absl::Status mapping_status =
160 ValidateChestObjectMapping(before_objects, before_chests);
161 if (!mapping_status.ok()) {
162 return mapping_status;
163 }
164 std::vector<std::optional<size_t>> before_ordinals(before_objects.size());
165 for (size_t ordinal = 0; ordinal < before_indices.size(); ++ordinal) {
166 before_ordinals[before_indices[ordinal]] = ordinal;
167 }
168 const absl::Status slots_status = ValidateRoomEventSlots(after_objects);
169 if (!slots_status.ok()) {
170 return slots_status;
171 }
172
173 std::vector<chest_data> after_chests;
174 after_chests.reserve(after_indices.size());
175 for (size_t index : after_indices) {
176 chest_data chest{0x34, after_objects[index].id_ == 0xFB1};
177 if (source_indices[index]) {
178 if (const auto ordinal = before_ordinals[*source_indices[index]]) {
179 chest.id = before_chests[*ordinal].id;
180 }
181 }
182 if (!chest_overrides.empty() && chest_overrides[index]) {
183 chest = *chest_overrides[index];
184 }
185 after_chests.push_back(chest);
186 }
187 return after_chests;
188}
189
190} // namespace yaze::zelda3
std::vector< size_t > StreamObjectIndices(const std::vector< RoomObject > &objects, bool include_locks=false)
Definition chest_edit.cc:16
bool IsChest(const RoomObject &object)
Definition chest_edit.cc:12
absl::Status ValidateRoomEventSlots(const std::vector< RoomObject > &objects)
Definition chest_edit.cc:32
Zelda 3 specific classes and functions.
absl::Status ValidateChestObjectMapping(const std::vector< RoomObject > &objects, const std::vector< chest_data > &chests)
Definition chest_edit.cc:74
std::optional< size_t > ChestIndexForObject(const std::vector< RoomObject > &objects, size_t object_index)
Definition chest_edit.cc:63
bool UsesRoomObjectStream(const RoomObject &object)
constexpr bool IsStatefulChestObjectId(int object_id)
constexpr size_t kMaxChests
absl::StatusOr< std::vector< chest_data > > PlanChestObjectEdit(const std::vector< RoomObject > &before_objects, const std::vector< chest_data > &before_chests, const std::vector< RoomObject > &after_objects, const std::vector< std::optional< size_t > > &source_indices, const std::vector< std::optional< chest_data > > &chest_overrides)
Definition chest_edit.cc:94
Legacy chest data structure.
Definition zelda.h:438
Treasure chest.
Definition zelda.h:425