6#include "absl/strings/str_format.h"
36 for (
size_t i = 0; i < normalized.
objects.size(); ++i) {
37 normalized.
objects[i].set_block_load_order(
38 target.
objects[i].block_load_order());
47 contents.
objects.size() > previous_object_count) {
48 return absl::ResourceExhaustedError(absl::StrFormat(
49 "Room replacement has %zu objects; keep at most %zu or do not grow "
50 "the destination's existing %zu objects",
52 previous_object_count));
55 return absl::ResourceExhaustedError(
56 "Changed room replacements support at most six chest records; "
57 "preserve legacy contents or exclude objects and chest rewards");
62 std::vector<std::optional<size_t>> origins(contents.
objects.size());
63 const auto chest_plan =
65 return chest_plan.status();
72 return absl::InvalidArgumentError(
73 "Room document has an invalid source room ID");
75 const auto& contents = document.
contents;
79 return absl::ResourceExhaustedError(
80 "Room document object or chest-record resource limit exceeded");
82 for (
const auto&
object : contents.objects) {
83 const int options =
static_cast<int>(
object.options());
84 const bool torch = (options & 8) != 0;
85 const bool block = (options & 4) != 0;
86 if (options < 0 || (options & ~63) != 0 || (torch && block)) {
87 return absl::InvalidArgumentError(
88 "Object has unsupported option flags");
92 }
else if (
object.x() > (torch ? 62 : 63) ||
93 object.y() > (torch ? 62 : 63) ||
object.GetLayerValue() > 1 ||
94 object.size() != 0 ||
object.id_ != (torch ? 0x150 : 0xE00)) {
95 return absl::InvalidArgumentError(
96 "Special object has unsupported ROM fields");
98 if (
object.block_behavior_layer() > 1 ||
99 object.torch_reserved_bit() > 1 ||
101 object.block_load_order() > 65535) {
102 return absl::InvalidArgumentError(
103 "Special object metadata is out of range");
109 return absl::ResourceExhaustedError(
"Room door limit exceeded");
111 for (
const auto& door : contents.doors) {
112 const unsigned type =
static_cast<unsigned>(door.type);
113 if (door.position > 15 ||
static_cast<unsigned>(door.direction) > 3 ||
114 type > 0x66 || (type & 1) != 0) {
115 return absl::InvalidArgumentError(
116 "Door contains unsupported ROM fields");
118 const auto bytes = door.EncodeBytes();
119 if (bytes.first != door.byte1 || bytes.second != door.byte2) {
120 return absl::InvalidArgumentError(
121 "Door raw bytes do not match its encodable properties");
127 return absl::ResourceExhaustedError(
"Room sprite limit exceeded");
129 for (
const auto& sprite : contents.sprites) {
131 sprite.id, sprite.x, sprite.y, sprite.subtype, sprite.layer,
136 if (contents.items.size() > 4096) {
137 return absl::ResourceExhaustedError(
"Room pot item limit exceeded");
139 for (
const auto& item : contents.items) {
143 if (item.position == 0xFFFF) {
144 return absl::InvalidArgumentError(
145 "Pot item position collides with the stream terminator");
151 if (m.layout > 7 || m.floor1 > 15 || m.floor2 > 15 ||
152 static_cast<unsigned>(m.bg2) > 8 || m.layer2_mode > 7 ||
153 static_cast<unsigned>(m.collision) > 7 || m.pit_target_layer > 3 ||
154 m.layer_merging.ID > 8 || m.layer_merging.Name.size() > 64 ||
155 std::any_of(m.staircase_planes.begin(), m.staircase_planes.end(),
156 [](uint8_t value) { return value > 3; })) {
157 return absl::InvalidArgumentError(
158 "Room metadata contains unencodable fields");
162 const bool dark = m.bg2 == background2::DarkRoom;
163 const auto& expected_merge =
164 zelda3::kLayerMergeTypeList[dark ? 8 : m.layer2_mode];
165 if (m.is_dark != dark || m.is_light != dark ||
166 (!dark &&
static_cast<uint8_t
>(m.bg2) != m.layer2_mode) ||
167 m.layer_merging != expected_merge) {
168 return absl::InvalidArgumentError(
169 "Room BG2 mode, darkness and derived layer settings disagree");
174 return absl::InvalidArgumentError(
"Collision tiles require has_data=true");
177 const auto& water = document.
water;
178 const bool any = std::any_of(water.tiles.begin(), water.tiles.end(),
179 [](uint8_t value) { return value != 0; });
180 if (water.has_data != any ||
181 std::any_of(water.tiles.begin(), water.tiles.end(),
182 [](uint8_t value) { return value > 1; }) ||
183 (water.sram_bit_mask & (water.sram_bit_mask - 1)) != 0) {
184 return absl::InvalidArgumentError(
185 "Water fill needs boolean tiles and a single SRAM bit");
188 return absl::OkStatus();
201 source.GetLayerValue());
202 object.set_options(source.options());
203 object.all_bgs_ = source.all_bgs_;
204 object.lit_ = source.lit_;
205 object.set_block_load_order(source.block_load_order());
206 object.set_block_behavior_layer(source.block_behavior_layer());
207 object.set_torch_reserved_bit(source.torch_reserved_bit());
208 source = std::move(
object);
229 return ValidateObjectReplacement(document.
contents, 0);
242 return absl::InvalidArgumentError(
243 "Invalid room transfer destination or domains");
251 return absl::InvalidArgumentError(
252 "Dungeon sprite graphics must be in range 0x00..0x4F");
260 auto& after = plan.
after;
266 for (
auto&
object : after.contents.objects) {
267 object =
object.CopyForNewPlacement();
268 object.SetRom(
nullptr);
290 after.water = source.
water;
298 for (
auto&
object : contents.objects)
299 object.SetRom(room.
rom());
311 for (
size_t tile = 0; tile < document.
water.
tiles.size(); ++tile) {
314 static_cast<int>(tile / 64),
true);
static absl::Status ValidateSpriteProperties(uint8_t id, int x, int y, int subtype, int layer, int key_drop)
static constexpr int kBlockLoadOrderNew
const CustomCollisionMap & custom_collision() const
void set_water_fill_sram_bit_mask(uint8_t mask)
void RestoreMetadataSnapshot(const MetadataSnapshot &snapshot)
const WaterFillZoneMap & water_fill_zone() const
void ClearWaterFillZone()
uint8_t spriteset() const
void MarkWaterFillDirty()
void MarkCustomCollisionDirty()
MetadataSnapshot CaptureMetadataSnapshot() const
void SetWaterFillTile(int x, int y, bool filled)
absl::Status ValidateDomains(const DungeonRoomDocument &document, uint16_t domains)
absl::Status ValidateObjectReplacement(const DungeonSelectionEditState &contents, size_t previous_object_count)
bool SameCollision(const zelda3::CustomCollisionMap &a, const zelda3::CustomCollisionMap &b)
bool SamePortableObjectContents(const DungeonSelectionEditState &source, const DungeonSelectionEditState &target)
bool SameWater(const zelda3::WaterFillZoneMap &a, const zelda3::WaterFillZoneMap &b)
Editors are the view controllers for the application.
constexpr uint16_t kTransferMetadata
constexpr uint16_t kTransferAll
constexpr uint16_t kTransferItems
absl::Status ValidateDungeonRoomDocument(const DungeonRoomDocument &document)
constexpr uint16_t kTransferDoors
DungeonSelectionEditState CaptureDungeonSelectionEditState(const zelda3::Room &room)
constexpr uint16_t kTransferCollision
constexpr uint8_t kSelectionSprites
constexpr uint8_t kSelectionObjects
constexpr uint16_t kTransferSprites
absl::StatusOr< DungeonRoomTransferPlan > PlanDungeonRoomTransfer(const zelda3::Room &target, const DungeonRoomDocument &source, DungeonRoomTransferOptions options)
constexpr size_t kMaxDungeonRoomDocumentObjects
uint8_t ChangedDungeonSelectionDomains(const DungeonSelectionEditState &a, const DungeonSelectionEditState &b)
constexpr uint8_t kSelectionDoors
bool SameDungeonRoomDocument(const DungeonRoomDocument &a, const DungeonRoomDocument &b)
void ApplyDungeonSelectionEditState(zelda3::Room &room, const DungeonSelectionEditState &state, uint8_t domains)
absl::Status ValidateDungeonRoomDocumentForInterchange(const DungeonRoomDocument &document)
constexpr uint16_t kTransferWater
void ApplyDungeonRoomDocument(zelda3::Room &room, const DungeonRoomDocument &document)
DungeonRoomDocument CaptureDungeonRoomDocument(const zelda3::Room &room)
constexpr uint16_t kTransferObjects
constexpr uint8_t kSelectionItems
constexpr size_t kMaxDungeonRoomDocumentChestRecords
absl::Status ValidateChestObjectMapping(const std::vector< RoomObject > &objects, const std::vector< chest_data > &chests)
constexpr int kMaxDungeonSpriteset
constexpr size_t kMaxTileObjects
bool UsesRoomObjectStream(const RoomObject &object)
constexpr size_t kMaxDoors
constexpr int kNumberOfRooms
absl::Status ValidateRoomObjectStreamEntryForSave(const RoomObject &object)
constexpr size_t kMaxChests
constexpr size_t kMaxTotalSprites
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)
#define RETURN_IF_ERROR(expr)
DungeonSelectionEditState contents
zelda3::CustomCollisionMap collision
zelda3::WaterFillZoneMap water
zelda3::Room::MetadataSnapshot metadata
DungeonRoomTransferOptions options
DungeonRoomDocument after
DungeonRoomDocument before
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
bool HasOverrides() const
std::array< uint8_t, 64 *64 > tiles