8#include "absl/strings/str_format.h"
20 if (std::find(values.begin(), values.end(), value) == values.end()) {
21 values.push_back(value);
27 const std::vector<SelectedEntity>& entities,
28 std::vector<size_t>& selected_objects,
29 std::vector<SelectedEntity>& selected_entities) {
30 for (
size_t index : objects) {
31 if (index >= state.
objects.size()) {
32 return absl::OutOfRangeError(
"Selected object is no longer available");
36 for (
const auto& entity : entities) {
38 switch (entity.type) {
39 case EntityType::Object:
40 if (entity.index >= state.
objects.size()) {
41 return absl::OutOfRangeError(
42 "Selected object is no longer available");
46 case EntityType::Door:
47 count = state.
doors.size();
49 case EntityType::Sprite:
52 case EntityType::Item:
53 count = state.
items.size();
55 case EntityType::None:
56 return absl::InvalidArgumentError(
57 "Selection contains an invalid entity");
59 return absl::InvalidArgumentError(
60 "Selection contains an unknown entity");
62 if (entity.index >= count) {
63 return absl::OutOfRangeError(
"Selected entity is no longer available");
67 return absl::OkStatus();
70template <
typename T,
typename Equal>
71bool SameVector(
const std::vector<T>& a,
const std::vector<T>& b, Equal equal) {
72 return a.size() == b.size() &&
73 std::equal(a.begin(), a.end(), b.begin(), equal);
77 const std::vector<chest_data>& b) {
78 return SameVector(a, b, [](
const auto& x,
const auto& y) {
79 return x.id == y.id && x.size == y.size;
84 const std::vector<zelda3::RoomObject>& b) {
85 return SameVector(a, b, [](
const auto& x,
const auto& y) {
86 return x.id_ == y.id_ && x.x_ == y.x_ && x.y_ == y.y_ &&
87 x.size_ == y.size_ && x.layer_ == y.layer_ &&
88 x.options() == y.options() && x.all_bgs_ == y.all_bgs_ &&
89 x.lit_ == y.lit_ && x.block_load_order() == y.block_load_order() &&
90 x.block_behavior_layer() == y.block_behavior_layer() &&
91 x.torch_reserved_bit() == y.torch_reserved_bit();
96 int maximum,
const char* label) {
97 const int64_t result = coordinate + delta;
98 if (result < 0 || result > maximum || result % step != 0) {
99 return absl::InvalidArgumentError(absl::StrFormat(
100 "%s cannot move to pixel %lld; expected 0..%d in %d-pixel steps", label,
101 static_cast<long long>(result), maximum, step));
103 return static_cast<int>(result);
116 if (
object.GetLayerValue() > 1) {
117 return absl::InvalidArgumentError(
"Special objects require layer 0 or 1");
121 (
object.x_ > 62 ||
object.y_ > 62)) {
122 return absl::InvalidArgumentError(
"Torch coordinates must be in 0..62");
124 return absl::OkStatus();
128 const auto type =
static_cast<unsigned>(door.
type);
129 if (type > 0x66 || (type & 1) != 0 ||
130 static_cast<unsigned>(door.
direction) > 3 ||
133 return absl::InvalidArgumentError(
134 "Selected door has invalid ROM properties");
137 if (dx == 0 && dy == 0)
138 return absl::OkStatus();
140 const int64_t target_x =
static_cast<int64_t
>(x) + dx;
141 const int64_t target_y =
static_cast<int64_t
>(y) + dy;
144 for (uint8_t position = 0; position < 16; ++position) {
147 const auto [next_x, next_y] =
150 if (next_x == target_x && next_y == target_y) {
155 return absl::OkStatus();
158 return absl::InvalidArgumentError(
159 "Door has no valid slot at the requested position; the selection was not "
166 16, 496,
"Sprite X"));
169 16, 496,
"Sprite Y"));
184 return absl::InvalidArgumentError(
"Invalid pot item position");
186 return absl::OkStatus();
189absl::Status
CheckGrowth(
size_t before,
size_t after,
size_t maximum,
191 if (after > maximum && after > before) {
192 return absl::ResourceExhaustedError(
193 absl::StrFormat(
"%s limit reached (%zu maximum)", label, maximum));
195 return absl::OkStatus();
200 std::sort(indices.begin(), indices.end(), std::greater<size_t>());
201 for (
size_t index : indices)
202 values.erase(values.begin() + index);
205std::vector<size_t>
EntityIndices(
const std::vector<SelectedEntity>& entities,
207 std::vector<size_t> indices;
208 for (
const auto& entity : entities) {
209 if (entity.type == type)
210 indices.push_back(entity.index);
224 for (
const auto& sprite : room.
GetSprites()) {
225 state.
sprites.push_back({sprite.id(), sprite.x(), sprite.y(),
226 sprite.subtype(), sprite.layer(),
227 sprite.key_drop(), sprite.deleted()});
237 if (!SameVector(a.
doors, b.
doors, [](
const auto& x,
const auto& y) {
238 return x.position == y.position && x.type == y.type &&
239 x.direction == y.direction && x.byte1 == y.byte1 &&
245 if (!SameVector(a.
items, b.
items, [](
const auto& x,
const auto& y) {
246 return x.position == y.position && x.item == y.item;
253 const zelda3::Room& room,
const std::vector<size_t>& objects,
254 const std::vector<SelectedEntity>& entities) {
256 std::vector<size_t> selected_objects;
257 std::vector<SelectedEntity> selected_entities;
258 RETURN_IF_ERROR(NormalizeSelection(state, objects, entities, selected_objects,
261 int min_x = std::numeric_limits<int>::max();
262 int min_y = std::numeric_limits<int>::max();
263 const auto include_anchor = [&](
int x,
int y) {
264 min_x = std::min(min_x, x);
265 min_y = std::min(min_y, y);
267 bool checked_chests =
false;
268 for (
size_t index : selected_objects) {
269 const auto&
object = state.objects[index];
270 std::optional<chest_data>
chest;
273 if (!checked_chests) {
276 checked_chests =
true;
279 if (!ordinal || *ordinal >= state.chests.size()) {
280 return absl::FailedPreconditionError(
281 "Selected chest has no contents record");
283 chest = state.chests[*ordinal];
285 clipboard.
objects.push_back(
object);
287 include_anchor(
object.x_ * 8,
object.y_ * 8);
289 for (
const auto& entity : selected_entities) {
290 switch (entity.type) {
291 case EntityType::Door: {
292 const auto& door = state.doors[entity.index];
293 clipboard.
doors.push_back(door);
294 const auto [x, y] = door.GetPixelCoords();
295 include_anchor(x, y);
298 case EntityType::Sprite: {
299 const auto& sprite = state.sprites[entity.index];
300 clipboard.
sprites.push_back(sprite);
301 include_anchor(sprite.x * 16, sprite.y * 16);
304 case EntityType::Item: {
305 const auto& item = state.items[entity.index];
306 clipboard.
items.push_back(item);
307 include_anchor(item.GetPixelX(), item.GetPixelY());
314 if (!clipboard.
empty()) {
331 auto& after = plan.
after;
334 std::vector<std::optional<size_t>> sources;
335 for (
size_t i = 0; i < after.objects.size(); ++i)
336 sources.push_back(i);
337 std::vector<std::optional<chest_data>> overrides(after.objects.size());
338 switch (request.
kind) {
342 overrides.resize(after.objects.size());
346 EntityType::Sprite));
349 after.selected_objects.clear();
350 after.selected_entities.clear();
353 if (dx == 0 && dy == 0)
355 for (
size_t index : after.selected_objects) {
358 for (
const auto& entity : after.selected_entities) {
359 if (entity.type == EntityType::Door) {
361 }
else if (entity.type == EntityType::Sprite) {
379 return absl::InvalidArgumentError(
"Clipboard is unavailable");
381 return absl::InvalidArgumentError(
382 "Clipboard chest metadata is incomplete");
384 after.selected_objects.clear();
385 after.selected_entities.clear();
386 for (
size_t i = 0; i < clipboard->
objects.size(); ++i) {
387 auto object = clipboard->
objects[i].CopyForNewPlacement();
391 return absl::InvalidArgumentError(
392 "Clipboard chest contents do not match its objects");
397 object.SetRom(room.
rom());
398 object.tiles_loaded_ =
false;
399 after.selected_objects.push_back(after.objects.size());
400 after.objects.push_back(std::move(
object));
401 sources.push_back(std::nullopt);
404 for (
auto door : clipboard->
doors) {
406 after.selected_entities.push_back(
407 {EntityType::Door, after.doors.size()});
408 after.doors.push_back(door);
410 for (
auto sprite : clipboard->
sprites) {
412 after.selected_entities.push_back(
413 {EntityType::Sprite, after.sprites.size()});
414 after.sprites.push_back(sprite);
416 for (
auto item : clipboard->
items) {
418 after.selected_entities.push_back(
419 {EntityType::Item, after.items.size()});
420 after.items.push_back(item);
425 return absl::InvalidArgumentError(
"Unknown selection edit operation");
436 after.objects, sources, overrides));
448 if (!SameObjects(current.objects, state.
objects)) {
451 if (!SameChests(current.chests, state.
chests)) {
461 std::vector<zelda3::Sprite> sprites;
462 sprites.reserve(state.
sprites.size());
463 for (
const auto& source : state.
sprites) {
465 static_cast<uint8_t
>(source.y),
466 static_cast<uint8_t
>(source.subtype),
467 static_cast<uint8_t
>(source.layer));
470 sprites.push_back(std::move(sprite));
static absl::Status ValidateSpriteProperties(uint8_t id, int x, int y, int subtype, int layer, int key_drop)
static std::pair< int, int > PositionToPixelCoords(uint8_t position, DoorDirection direction)
Convert encoded position to pixel coordinates.
static bool IsValidPosition(uint8_t position, DoorDirection direction)
Check if a position is valid for door placement.
const std::vector< chest_data > & GetChests() const
const std::vector< Door > & GetDoors() const
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< RoomObject > & GetTileObjects() const
void MarkObjectStreamDirty()
void SetTileObjects(const std::vector< RoomObject > &objects)
const std::vector< PotItem > & GetPotItems() const
A class for managing sprites in the overworld and underworld.
auto set_key_drop(int key)
auto set_deleted(bool deleted)
struct chest chest
Treasure chest.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
bool SameChests(const std::vector< chest_data > &a, const std::vector< chest_data > &b)
absl::StatusOr< int > TranslateCoordinate(int64_t coordinate, int delta, int step, int maximum, const char *label)
absl::Status TranslateDoor(zelda3::Room::Door &door, int dx, int dy)
absl::Status TranslateItem(zelda3::PotItem &item, int dx, int dy)
absl::Status CheckGrowth(size_t before, size_t after, size_t maximum, const char *label)
bool SameObjects(const std::vector< zelda3::RoomObject > &a, const std::vector< zelda3::RoomObject > &b)
void AppendUnique(std::vector< T > &values, const T &value)
absl::Status NormalizeSelection(const DungeonSelectionEditState &state, const std::vector< size_t > &objects, const std::vector< SelectedEntity > &entities, std::vector< size_t > &selected_objects, std::vector< SelectedEntity > &selected_entities)
absl::Status TranslateObject(zelda3::RoomObject &object, int dx, int dy)
std::vector< size_t > EntityIndices(const std::vector< SelectedEntity > &entities, EntityType type)
void RemoveIndices(std::vector< T > &values, std::vector< size_t > indices)
bool SameVector(const std::vector< T > &a, const std::vector< T > &b, Equal equal)
absl::Status TranslateSprite(DungeonSpriteSnapshot &sprite, int dx, int dy)
Editors are the view controllers for the application.
absl::StatusOr< DungeonSelectionClipboard > CopyDungeonSelection(const zelda3::Room &room, const std::vector< size_t > &objects, const std::vector< SelectedEntity > &entities)
absl::StatusOr< DungeonSelectionEditPlan > PlanDungeonSelectionEdit(const zelda3::Room &room, const DungeonSelectionEditRequest &request)
DungeonSelectionEditState CaptureDungeonSelectionEditState(const zelda3::Room &room)
constexpr uint8_t kSelectionSprites
constexpr uint8_t kSelectionObjects
uint8_t ChangedDungeonSelectionDomains(const DungeonSelectionEditState &a, const DungeonSelectionEditState &b)
constexpr uint8_t kSelectionDoors
void ApplyDungeonSelectionEditState(zelda3::Room &room, const DungeonSelectionEditState &state, uint8_t domains)
EntityType
Type of entity that can be selected in the dungeon editor.
constexpr uint8_t kSelectionItems
absl::Status ValidateChestObjectMapping(const std::vector< RoomObject > &objects, const std::vector< chest_data > &chests)
std::optional< size_t > ChestIndexForObject(const std::vector< RoomObject > &objects, size_t object_index)
constexpr size_t kMaxTileObjects
constexpr std::optional< uint16_t > EncodePotItemPosition(int x, int y, uint16_t original=0)
bool UsesRoomObjectStream(const RoomObject &object)
constexpr size_t kMaxDoors
constexpr bool IsStatefulChestObjectId(int object_id)
absl::Status ValidateRoomObjectStreamEntryForSave(const RoomObject &object)
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)
std::vector< zelda3::Room::Door > doors
std::vector< zelda3::RoomObject > objects
std::vector< DungeonSpriteSnapshot > sprites
std::vector< std::optional< chest_data > > object_chests
std::vector< zelda3::PotItem > items
DungeonSelectionEditKind kind
DungeonSelectionEditState before
DungeonSelectionEditState after
DungeonSelectionEditKind kind
std::vector< SelectedEntity > entities
std::vector< size_t > objects
const DungeonSelectionClipboard * clipboard
std::vector< SelectedEntity > selected_entities
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::vector< size_t > selected_objects
Represents a door in a dungeon room.
uint8_t byte1
Original ROM byte 1 (position data)
DoorType type
Door type (determines appearance/behavior)
std::pair< uint8_t, uint8_t > EncodeBytes() const
Encode door data for ROM storage.
DoorDirection direction
Which wall the door is on.
uint8_t position
Encoded position (5-bit, 0-31)
std::pair< int, int > GetPixelCoords() const
Get pixel coordinates for this door.
uint8_t byte2
Original ROM byte 2 (type + direction)