3#include <initializer_list>
9#include "nlohmann/json.hpp"
15using Json = nlohmann::json;
17void Keys(
const Json& value, std::initializer_list<const char*> keys) {
18 if (!value.is_object() || value.size() != keys.size()) {
19 throw std::invalid_argument(
20 "Unexpected, missing, or unknown room document fields");
22 for (
const char* key : keys) {
23 if (!value.contains(key)) {
24 throw std::invalid_argument(std::string(
"Missing required field: ") +
31 if (value.is_number_unsigned()) {
32 const uint64_t number = value.get<uint64_t>();
33 if (number <=
static_cast<uint64_t
>(maximum) &&
34 (minimum <= 0 || number >=
static_cast<uint64_t
>(minimum))) {
35 return static_cast<int>(number);
37 }
else if (value.is_number_integer()) {
38 const int64_t number = value.get<int64_t>();
39 if (number >= minimum && number <= maximum)
40 return static_cast<int>(number);
42 throw std::invalid_argument(
43 "Expected an integer inside the field's ROM range");
47 if (!value.is_boolean())
48 throw std::invalid_argument(
"Expected a boolean field");
49 return value.get<
bool>();
53 if (!value.is_array() || value.size() > maximum) {
54 throw std::invalid_argument(
"Expected an array within its room limit");
62 if (value.size() != N)
63 throw std::invalid_argument(
"Incorrect fixed array length");
64 std::array<uint8_t, N> result{};
65 for (
size_t i = 0; i < N; ++i)
66 result[i] =
Integer(value[i], 0, maximum);
78 {
"bg2",
static_cast<int>(m.
bg2)},
88 {
"collision",
static_cast<int>(m.
collision)},
89 {
"effect",
static_cast<int>(m.
effect)},
90 {
"tag1",
static_cast<int>(m.
tag1)},
91 {
"tag2",
static_cast<int>(m.
tag2)},
118 "staircase_planes"});
121 m.blockset =
Integer(j.at(
"blockset"), 0, 255);
122 m.spriteset =
Integer(j.at(
"spriteset"), 0, 255);
123 m.layout =
Integer(j.at(
"layout"), 0, 7);
124 m.floor1 =
Integer(j.at(
"floor1"), 0, 15);
125 m.floor2 =
Integer(j.at(
"floor2"), 0, 15);
126 m.message =
Integer(j.at(
"message"), 0, 65535);
128 m.layer2_mode =
Integer(j.at(
"layer2_mode"), 0, 7);
129 const auto& merging = j.at(
"layer_merging");
130 Keys(merging, {
"id",
"name",
"on_top",
"translucent",
"visible"});
131 if (!merging.at(
"name").is_string() ||
132 merging.at(
"name").get_ref<
const std::string&>().size() > 64) {
133 throw std::invalid_argument(
"Invalid layer merging name");
136 Integer(merging.at(
"id"), 0, 8), merging.at(
"name").get<std::string>(),
138 Boolean(merging.at(
"translucent")));
139 m.is_dark =
Boolean(j.at(
"is_dark"));
140 m.is_light =
Boolean(j.at(
"is_light"));
146 m.holewarp =
Integer(j.at(
"holewarp"), 0, 255);
147 m.pit_target_layer =
Integer(j.at(
"pit_target_layer"), 0, 3);
148 m.staircase_rooms = ByteArray<4>(j.at(
"staircase_rooms"));
149 m.staircase_planes = ByteArray<4>(j.at(
"staircase_planes"), 3);
159 Json j{{
"format",
"yaze.room"},
162 {
"metadata", MetadataToJson(document.
metadata)},
182 j[
"collision"][
"overrides"].push_back(offset);
186 j[
"objects"].push_back(
191 {
"layer", o.GetLayerValue()},
192 {
"options",
static_cast<int>(o.options())},
193 {
"all_bgs", o.all_bgs_},
197 {
"block_behavior_layer", o.block_behavior_layer()},
198 {
"torch_reserved_bit", o.torch_reserved_bit()}});
201 j[
"chests"].push_back({{
"id",
chest.id}, {
"big",
chest.size}});
204 j[
"doors"].push_back({{
"position", door.position},
205 {
"type",
static_cast<int>(door.type)},
206 {
"direction",
static_cast<int>(door.direction)},
207 {
"byte1", door.byte1},
208 {
"byte2", door.byte2}});
211 j[
"sprites"].push_back({{
"id", s.id},
214 {
"subtype", s.subtype},
216 {
"key_drop", s.key_drop},
217 {
"deleted", s.deleted}});
220 j[
"pot_items"].push_back(
221 {{
"position", item.position}, {
"item", item.item}});
223 auto serialized = j.dump(2);
225 return absl::ResourceExhaustedError(
"Room document exceeds 1 MiB");
229 return absl::InvalidArgumentError(
230 std::string(
"Cannot serialize room document: ") + error.
what());
235 const std::string& text) {
237 return absl::ResourceExhaustedError(
"Room document exceeds 1 MiB");
240 std::vector<std::set<std::string>> object_keys;
242 text, [&](
int depth, Json::parse_event_t event,
Json& value) {
244 throw std::invalid_argument(
"Room document nesting is too deep");
245 if (event == Json::parse_event_t::object_start)
246 object_keys.emplace_back();
247 if (event == Json::parse_event_t::key &&
248 !object_keys.back().insert(value.
get<std::string>()).second) {
249 throw std::invalid_argument(
"Duplicate room document field");
251 if (event == Json::parse_event_t::object_end)
252 object_keys.pop_back();
255 Keys(j, {
"format",
"version",
"source_room_id",
"metadata",
"objects",
256 "chests",
"doors",
"sprites",
"pot_items",
"collision",
"water"});
257 if (!j.at(
"format").is_string() || j.at(
"format") !=
"yaze.room" ||
258 Integer(j.at(
"version"), 1, 1) != 1) {
259 throw std::invalid_argument(
260 "Unsupported room document format or version");
265 document.
metadata = MetadataFromJson(j.at(
"metadata"));
269 {
"id",
"x",
"y",
"size",
"layer",
"options",
"all_bgs",
"lit",
270 "block_load_order",
"block_behavior_layer",
"torch_reserved_bit"});
272 Integer(o.at(
"id"), 0, 4095), Integer(o.at(
"x"), 0, 63),
273 Integer(o.at(
"y"), 0, 63), Integer(o.at(
"size"), 0, 255),
274 Integer(o.at(
"layer"), 0, 2));
277 object.all_bgs_ = Boolean(o.at(
"all_bgs"));
278 object.lit_ = Boolean(o.at(
"lit"));
280 (void)Integer(o.at(
"block_load_order"), -1, 65535);
282 object.set_block_behavior_layer(
283 Integer(o.at(
"block_behavior_layer"), 0, 1));
284 object.set_torch_reserved_bit(Integer(o.at(
"torch_reserved_bit"), 0, 1));
289 Keys(c, {
"id",
"big"});
291 {
static_cast<uint8_t
>(Integer(c.at(
"id"), 0, 255)),
292 Boolean(c.at(
"big"))});
295 Keys(d, {
"position",
"type",
"direction",
"byte1",
"byte2"});
297 {
static_cast<uint8_t
>(Integer(d.at(
"position"), 0, 15)),
300 static_cast<uint8_t
>(Integer(d.at(
"byte1"), 0, 255)),
301 static_cast<uint8_t
>(Integer(d.at(
"byte2"), 0, 255))});
304 Keys(s, {
"id",
"x",
"y",
"subtype",
"layer",
"key_drop",
"deleted"});
306 {
static_cast<uint8_t
>(Integer(s.at(
"id"), 0, 255)),
307 Integer(s.at(
"x"), 0, 31), Integer(s.at(
"y"), 0, 31),
308 Integer(s.at(
"subtype"), 0, 31), Integer(s.at(
"layer"), 0, 1),
309 Integer(s.at(
"key_drop"), 0, 2), Boolean(s.at(
"deleted"))});
311 for (
const auto& i : Array(j.at(
"pot_items"), 4096)) {
312 Keys(i, {
"position",
"item"});
314 {
static_cast<uint16_t
>(Integer(i.at(
"position"), 0, 65535)),
315 static_cast<uint8_t
>(Integer(i.at(
"item"), 0, 255))});
317 const auto& collision = j.at(
"collision");
318 if (collision.contains(
"overrides")) {
319 Keys(collision, {
"has_data",
"tiles",
"overrides"});
321 Keys(collision, {
"has_data",
"tiles"});
324 document.
collision.
tiles = ByteArray<4096>(collision.at(
"tiles"));
325 if (collision.contains(
"overrides")) {
327 for (
const auto& value : Array(collision.at(
"overrides"), 4096)) {
328 const int offset = Integer(value, 0, 4095);
329 if (!seen.insert(offset).second) {
330 throw std::invalid_argument(
"Duplicate collision override offset");
335 const auto& water = j.at(
"water");
336 Keys(water, {
"has_data",
"sram_bit_mask",
"tiles"});
339 document.
water.
tiles = ByteArray<4096>(water.at(
"tiles"), 1);
343 return absl::InvalidArgumentError(std::string(
"Invalid room document: ") +
345 }
catch (
const std::invalid_argument& error) {
346 return absl::InvalidArgumentError(std::string(
"Invalid room document: ") +
const char * what() const noexcept override
static Json parse(const std::string &)
static constexpr int kBlockLoadOrderNew
zelda3_bg2_effect
Background layer 2 effects.
const Json & Array(const Json &value, size_t maximum)
Json MetadataToJson(const zelda3::Room::MetadataSnapshot &m)
std::array< uint8_t, N > ByteArray(const Json &value, int maximum=255)
int Integer(const Json &value, int minimum, int maximum)
zelda3::Room::MetadataSnapshot MetadataFromJson(const Json &j)
bool Boolean(const Json &value)
void Keys(const Json &value, std::initializer_list< const char * > keys)
Editors are the view controllers for the application.
constexpr size_t kMaxDungeonRoomDocumentObjects
absl::Status ValidateDungeonRoomDocumentForInterchange(const DungeonRoomDocument &document)
absl::StatusOr< std::string > SerializeDungeonRoomDocument(const DungeonRoomDocument &document)
absl::StatusOr< DungeonRoomDocument > ParseDungeonRoomDocument(const std::string &json)
constexpr size_t kMaxDungeonRoomDocumentChestRecords
constexpr size_t kMaxDungeonRoomDocumentBytes
DoorType
Door types from ALTTP.
constexpr size_t kMaxDoors
constexpr int kNumberOfRooms
DoorDirection
Door direction on room walls.
constexpr size_t kMaxTotalSprites
#define RETURN_IF_ERROR(expr)
DungeonSelectionEditState contents
zelda3::CustomCollisionMap collision
zelda3::WaterFillZoneMap water
zelda3::Room::MetadataSnapshot metadata
std::vector< zelda3::Room::Door > doors
std::vector< DungeonSpriteSnapshot > sprites
std::vector< zelda3::PotItem > items
std::vector< zelda3::RoomObject > objects
std::vector< chest_data > chests
std::array< uint8_t, 64 *64 > tiles
std::bitset< 64 *64 > overrides
bool HasOverride(size_t offset) const
std::array< uint8_t, 64 *64 > tiles