13#include <unordered_set>
16#include "absl/status/status.h"
17#include "absl/strings/str_format.h"
18#include "absl/types/span.h"
60#include "imgui/imgui.h"
80 using Apply = std::function<absl::Status(uint8_t, uint8_t)>;
87 apply_(std::move(apply)) {}
88 absl::Status
Undo()
override {
return apply_(after_, before_); }
89 absl::Status
Redo()
override {
return apply_(before_, after_); }
91 return absl::StrFormat(
"Room graphics group %02X slot %d", group_, slot_);
108 return absl::FailedPreconditionError(
"No loaded graphics session");
111 if (!room || slot < 3 || slot > 6 || sheet_id < 0 ||
113 return absl::InvalidArgumentError(
"Choose a roomset slot (3-6) and sheet");
118 return absl::InvalidArgumentError(
119 "Room backgrounds require a loaded 3bpp sheet");
121 const int group = room->blockset();
123 return absl::OutOfRangeError(
"Room graphics group is unavailable");
126 const auto after =
static_cast<uint8_t
>(sheet_id);
127 if (before == after) {
128 return absl::OkStatus();
131 auto apply = [
this, owner =
game_data_, source_rom =
rom_, group, slot](
132 uint8_t expected, uint8_t value) -> absl::Status {
134 return absl::FailedPreconditionError(
135 "Graphics assignment belongs to another session");
138 if (assigned != expected) {
139 return absl::FailedPreconditionError(
140 "Graphics assignment changed elsewhere; Undo was not applied");
145 affected.MarkGraphicsDirty();
149 return absl::OkStatus();
153 group, slot, before, after, std::move(apply)));
154 return absl::OkStatus();
160 room_viewers_.ForEach([](
int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
162 viewer->CancelMinecartGesture();
163 viewer->object_interaction().HandleMouseRelease();
169 viewer->CancelMinecartGesture();
170 viewer->object_interaction().HandleMouseRelease();
194 viewer->TriggerChangePing();
199 description.empty() ?
"Undid last dungeon edit"
200 : absl::StrFormat(
"Undid: %s", description),
216 viewer->TriggerChangePing();
221 description.empty() ?
"Redid last dungeon edit"
222 : absl::StrFormat(
"Redid: %s", description),
233 return viewer->object_interaction().HandleDeleteSelected();
235 return absl::OkStatus();
240 return viewer->object_interaction().HandleCopySelected();
242 return absl::OkStatus();
247 return viewer->object_interaction().HandlePasteObjects();
249 return absl::OkStatus();
260 return static_cast<size_t>(domain) -
268 [](
const auto& x,
const auto& y) {
269 return x.position == y.position && x.type == y.type &&
270 x.direction == y.direction && x.byte1 == y.byte1 &&
275 [](
const auto& x,
const auto& y) {
276 return x.position == y.position && x.item == y.item;
281 const zelda3::Room& room,
const std::vector<SelectedEntity>& selected) {
282 std::vector<SelectedEntity> valid;
283 for (
const auto entity : selected) {
284 if ((entity.type == EntityType::Door &&
285 entity.index < room.
GetDoors().size()) ||
286 (entity.type == EntityType::Sprite &&
288 (entity.type == EntityType::Item &&
290 valid.push_back(entity);
303 [
this](
int target,
int source,
const std::string& json,
311 [
this](
int room_id,
size_t index) {
313 if (!room || room->rom() !=
rom_ || index >= room->GetDoors().
size()) {
316 const auto door = room->
GetDoors()[index];
321 target->object_interaction().SetCurrentRoom(&
rooms_, room_id);
322 target->object_interaction().ClearSelection();
323 target->object_interaction().SelectEntity(EntityType::Door, index);
324 const auto [x, y, width, height] = door.GetEditorBounds();
325 target->ScrollToTile((x + width / 2) / 8, (y + height / 2) / 8);
326 target->TriggerCanvasPingRect(x, y, width, height);
341 [
this](
const absl::Status& status) {
344 absl::StrFormat(
"Edit not applied: %s", status.message()),
353 [
this](
const std::vector<RoomMetadataRequest>& requests) {
357 [
this](
int room_id,
size_t index, uint8_t item_id,
bool big_chest) {
358 return EditChest(room_id, index, item_id, big_chest);
367 [
this](
int room_id,
const std::vector<zelda3::RoomObject>& objects,
368 const std::vector<chest_data>& chests) {
382 absl::StrFormat(
"Edit not applied: %s", status.message()),
389 [
this](
int id,
const std::vector<zelda3::Room::Door>& doors) {
392 door_handler.SetMutationErrorCallback([
this](
const absl::Status& status) {
395 absl::StrFormat(
"Edit not applied: %s", status.message()),
399 auto& sprite_handler =
402 [
this](
int id,
const std::vector<zelda3::Sprite>& sprites) {
405 sprite_handler.SetMutationErrorCallback([
this](
const absl::Status& status) {
408 absl::StrFormat(
"Edit not applied: %s", status.message()),
415 [
this](
int id,
const std::vector<zelda3::PotItem>& items) {
418 item_handler.SetMutationErrorCallback([
this](
const absl::Status& status) {
421 absl::StrFormat(
"Edit not applied: %s", status.message()),
437 const auto domain = ctx->last_mutation_domain;
440 }
else if (IsEntityMutation(domain)) {
459 const auto mode = interaction.mode_manager().GetMode();
461 rooms_[rid].MarkObjectsDirty();
462 rooms_[rid].RenderRoomGraphics();
466 }
else if (IsEntityMutation(domain)) {
472 room.MarkObjectsDirty();
474 room.RenderRoomGraphics();
479 interaction.mode_manager().GetModeState().is_painting)) {
484 interaction.mode_manager().GetModeState().is_painting)) {
494 const auto& room =
rooms_[room_id];
496 state.
doors = room.GetDoors();
498 for (
const auto& sprite : room.GetSprites()) {
499 state.
sprites.push_back({sprite.id(), sprite.x(), sprite.y(),
500 sprite.subtype(), sprite.layer(),
501 sprite.key_drop(), sprite.deleted()});
504 state.
items = room.GetPotItems();
509 .entity_coordinator()
512 ->current_room_id == room_id)) {
513 auto& interaction = viewer->object_interaction();
514 state.
entities = interaction.entity_coordinator().GetSelectedEntities();
515 if (state.
entities.empty() && interaction.HasEntitySelection()) {
516 state.
entities.push_back(interaction.GetSelectedEntity());
519 state.
objects = interaction.GetSelectedObjectIndices();
530 if (pending.room_id >= 0) {
533 pending.room_id = room_id;
543 if (pending.room_id != room_id) {
547 if (SameEntityData(pending.before, after)) {
553 const bool removed = after.doors.size() < pending.before.doors.size() ||
554 after.sprites.size() < pending.before.sprites.size() ||
555 after.items.size() < pending.before.items.size();
560 std::erase_if(after.entities,
561 [
type](
const auto& entity) { return entity.type == type; });
564 .entity_coordinator()
568 viewer->object_interaction().entity_coordinator().SetSelectedEntities(
573 room_id, domain, std::move(pending.before), std::move(after),
576 return RestoreRoomEntities(rid, restored_domain, snapshot);
585 if (pending.room_id >= 0) {
594 return absl::InvalidArgumentError(
"Invalid dungeon entity undo target");
598 return absl::FailedPreconditionError(
599 "Dungeon entity undo room is not loaded");
602 room->GetDoors() = snapshot.
doors;
603 room->MarkObjectStreamDirty();
605 std::vector<zelda3::Sprite> restored;
606 restored.reserve(snapshot.
sprites.size());
607 for (
const auto& sprite : snapshot.
sprites) {
608 restored.emplace_back(sprite.id, sprite.x, sprite.y, sprite.subtype,
610 restored.back().set_key_drop(sprite.key_drop);
611 restored.back().set_deleted(sprite.deleted);
613 room->GetSprites() = std::move(restored);
614 room->MarkSpritesDirty();
616 room->GetPotItems() = snapshot.
items;
617 room->MarkPotItemsDirty();
619 room->MarkObjectsDirty();
621 room->RenderRoomGraphics();
626 if (viewer->object_interaction()
627 .entity_coordinator()
630 ->current_room_id == room_id) {
631 auto& interaction = viewer->object_interaction();
632 interaction.CancelPlacement();
633 std::vector<size_t> objects;
634 for (
size_t index : snapshot.
objects) {
635 if (index < room->GetTileObjects().size()) {
636 objects.push_back(index);
639 interaction.SetSelectedObjects(objects);
640 interaction.entity_coordinator().SetSelectedEntities(
641 ValidEntitySelection(*room, snapshot.
entities));
642 viewer->TriggerChangePing();
646 return absl::OkStatus();
650 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
656 "BeginUndoSnapshot called twice without FinalizeUndoAction. "
657 "Previous snapshot for room %d is being leaked. Finalizing now.",
673 viewer->object_interaction().GetSelectedObjectIndices();
681 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
684 auto after_objects =
rooms_[room_id].GetTileObjects();
685 std::vector<size_t> after_selection;
689 after_selection = viewer->object_interaction().GetSelectedObjectIndices();
692 auto action = std::make_unique<DungeonObjectsAction>(
696 rooms_[room_id].GetChests(),
697 [
this](
int rid,
const std::vector<zelda3::RoomObject>& objects,
698 const std::vector<size_t>& selected_indices,
699 const std::vector<chest_data>& chests) {
700 return RestoreRoomObjects(rid, objects, selected_indices, chests);
715 int room_id,
const std::vector<zelda3::RoomObject>& objects,
716 const std::vector<size_t>& selected_indices,
717 const std::vector<chest_data>& chests) {
719 if (!loaded_room || loaded_room->rom() !=
rom_) {
720 return absl::FailedPreconditionError(
"Room objects are not loaded");
722 auto& room = *loaded_room;
723 if (room.GetChests().size() != chests.size() ||
725 chests.begin(), chests.end(), room.GetChests().begin(),
726 [](
auto a,
auto b) { return a.id == b.id && a.size == b.size; })) {
727 room.GetChests() = chests;
728 room.MarkChestsDirty();
730 const auto previous_objects = room.GetTileObjects();
731 room.SetTileObjects(objects);
732 room.RenderRoomGraphics();
736 std::vector<size_t> valid_selection;
737 for (
size_t index : selected_indices) {
738 if (index < objects.size()) {
739 valid_selection.push_back(index);
742 viewer->object_interaction().SetSelectedObjects(valid_selection);
743 viewer->TriggerObjectChangePing(previous_objects, objects);
750 return absl::OkStatus();
754 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
770 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
773 auto after =
rooms_[room_id].custom_collision();
780 auto action = std::make_unique<DungeonCustomCollisionAction>(
783 RestoreRoomCustomCollision(rid, map);
796 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
799 auto& room =
rooms_[room_id];
800 room.custom_collision() = map;
801 room.MarkCustomCollisionDirty();
827 : entries_(std::move(entries)) {}
833 for (
const auto& entry : entries_) {
834 entry.room->custom_collision() = entry.map;
836 entry.room->MarkCustomCollisionDirty();
838 entry.room->ClearCustomCollisionDirty();
847 bool committed_ =
false;
856 if (!room || !
rom_ ||
858 return absl::FailedPreconditionError(
859 "Load a room with expanded collision support");
861 if (!CollisionMapsEqual(room->custom_collision(), expected)) {
862 return absl::FailedPreconditionError(
863 "Collision changed; preview the stamp again");
866 if (CollisionMapsEqual(expected, after))
867 return absl::OkStatus();
869 const auto before = expected;
871 auto action = std::make_unique<DungeonCustomCollisionAction>(
872 room_id, before, after,
876 CollisionBatchRollback rollback(
877 {{room, before, room->custom_collision_dirty()}});
878 room->custom_collision() = after;
879 room->MarkCustomCollisionDirty();
882 }
catch (
const std::exception& error) {
883 return absl::InternalError(
884 absl::StrFormat(
"Could not record rail undo: %s", error.what()));
889 return absl::OkStatus();
893 const std::vector<zelda3::TrackCollisionResult>& preview,
895 if (preview.empty()) {
896 return absl::InvalidArgumentError(
"Minecart collision preview is empty");
899 std::unordered_set<int> seen_room_ids;
900 std::vector<DungeonCustomCollisionSnapshot> before;
901 std::vector<DungeonCustomCollisionSnapshot> after;
902 std::vector<CollisionRollbackEntry> rollback_entries;
903 before.reserve(preview.size());
904 after.reserve(preview.size());
905 rollback_entries.reserve(preview.size());
908 for (
const auto& expected : preview) {
909 const int room_id = expected.room_id;
911 return absl::OutOfRangeError(absl::StrFormat(
912 "Minecart collision room 0x%03X is out of range", room_id));
914 if (!seen_room_ids.insert(room_id).second) {
915 return absl::InvalidArgumentError(absl::StrFormat(
916 "Minecart collision preview contains duplicate room 0x%03X",
921 if (room ==
nullptr) {
922 return absl::FailedPreconditionError(absl::StrFormat(
923 "Minecart collision room 0x%03X is not loaded", room_id));
926 return absl::FailedPreconditionError(absl::StrFormat(
927 "Room 0x%03X already has custom collision; generation will not "
934 current.room_id = room_id;
935 if (!current.collision_map.has_data || current.tiles_generated <= 0) {
936 return absl::FailedPreconditionError(absl::StrFormat(
937 "Room 0x%03X no longer contains supported minecart track pieces",
940 if (!CollisionMapsEqual(current.collision_map, expected.collision_map) ||
941 current.tiles_generated != expected.tiles_generated ||
942 current.stop_count != expected.stop_count ||
943 current.corner_count != expected.corner_count ||
944 current.switch_count != expected.switch_count) {
945 return absl::FailedPreconditionError(absl::StrFormat(
946 "Minecart collision preview for room 0x%03X is stale; preview "
947 "again before applying",
952 after.push_back({room_id, current.collision_map});
953 rollback_entries.push_back(
961 [](
const auto& pending) { return pending.room_id >= 0; })) {
962 return absl::FailedPreconditionError(
963 "Finish the current dungeon edit before applying minecart collision");
966 auto action = std::make_unique<DungeonCustomCollisionBatchAction>(
968 [
this](
const std::vector<DungeonCustomCollisionSnapshot>& snapshots) {
972 CollisionBatchRollback rollback(std::move(rollback_entries));
973 for (
const auto& snapshot : after) {
976 room->MarkCustomCollisionDirty();
980 }
catch (
const std::exception& error) {
981 return absl::InternalError(absl::StrFormat(
982 "Could not record minecart collision undo: %s", error.what()));
989 for (
const auto& snapshot : after) {
991 viewer->TriggerChangePing();
994 return absl::OkStatus();
998 const std::vector<DungeonCustomCollisionSnapshot>& snapshots) {
999 std::unordered_set<int> seen_room_ids;
1000 for (
const auto& snapshot : snapshots) {
1002 return absl::OutOfRangeError(
"Collision undo room ID is out of range");
1004 if (!seen_room_ids.insert(snapshot.room_id).second) {
1005 return absl::InvalidArgumentError(
1006 "Collision undo contains a duplicate room ID");
1009 return absl::FailedPreconditionError(
1010 "Collision undo target room is not loaded");
1014 for (
const auto& snapshot : snapshots) {
1017 room.MarkCustomCollisionDirty();
1019 viewer->TriggerChangePing();
1026 return absl::OkStatus();
1037 for (
size_t i = 0; i < zone.tiles.size(); ++i) {
1038 if (zone.tiles[i] != 0) {
1039 snap.
offsets.push_back(
static_cast<uint16_t
>(i));
1048 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
1064 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
1067 auto after = MakeWaterFillSnapshot(
rooms_[room_id]);
1075 auto action = std::make_unique<DungeonWaterFillAction>(
1078 RestoreRoomWaterFill(rid, snap);
1088 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()))
1091 auto& room =
rooms_[room_id];
1092 room.ClearWaterFillZone();
1094 for (uint16_t off : snap.
offsets) {
1095 const int x =
static_cast<int>(off % 64);
1096 const int y =
static_cast<int>(off / 64);
1097 room.SetWaterFillTile(x, y,
true);
1099 room.MarkWaterFillDirty();
void set_dirty(bool dirty)
const auto & vector() const
InteractionContext * context() const
Get the interaction context.
void SetDoorMutationPreflight(DoorMutationPreflight callback)
void SetChestEditCallback(ChestEditCallback callback)
void SetMetadataBatchEditCallback(MetadataBatchEditCallback callback)
void SetDoorConnectionCallbacks(DoorConnectionPreviewCallback preview, DoorConnectionApplyCallback apply)
void SetRoomTransferCallbacks(RoomDocumentExportCallback export_document, RoomTransferPreviewCallback preview, RoomTransferApplyCallback apply)
DungeonObjectInteraction & object_interaction()
void SetChestDeleteCallback(ChestDeleteCallback callback)
void SetMetadataEditCallback(MetadataEditCallback callback)
void SetDoorConnectionNavigationCallback(std::function< void(int, size_t)> callback)
void BeginCollisionUndoSnapshot(int room_id)
class MinecartTrackEditorPanel * minecart_track_editor_panel_
absl::Status EditRoomGraphicsSheet(int room_id, int slot, int sheet_id)
void ConfigureViewerUndoHooks(DungeonCanvasViewer *viewer)
absl::Status PreflightObjectMutation(int room_id, const std::vector< zelda3::RoomObject > &objects, const std::vector< chest_data > &chests, bool check_streams=true)
void RestoreRoomWaterFill(int room_id, const WaterFillSnapshot &snap)
absl::Status RestoreRoomEntities(int room_id, MutationDomain domain, const DungeonEntitySnapshot &snapshot)
PendingUndo pending_undo_
void FinalizeSelectionUndoAction()
void BeginEntityUndoSnapshot(int room_id, MutationDomain domain)
absl::Status ApplyRoomTransfer(const DungeonRoomTransferPlan &plan)
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
DungeonEntitySnapshot CaptureRoomEntities(int room_id, MutationDomain domain)
absl::Status CommitSelectionEdit(const DungeonSelectionEditPlan &plan, bool continuous)
absl::Status PreflightPotItemPlacement(int room_id, const std::vector< zelda3::PotItem > &items)
void FinalizeWaterFillUndoAction(int room_id)
absl::Status ApplyDoorConnection(const DungeonConnectionPlan &plan)
absl::Status Copy() override
absl::Status EditRoomMetadataBatch(const std::vector< RoomMetadataRequest > &requests)
bool IsWorkbenchWorkflowEnabled() const
void OnRoomSelected(int room_id, bool request_focus=true)
void FinalizePendingUndoActions()
absl::Status ApplyMinecartCollisionStamp(int room_id, const zelda3::CustomCollisionMap &expected, const MinecartCollisionStamp &stamp)
void FinalizeEntityUndoAction(int room_id, MutationDomain domain)
PendingCollisionUndo pending_collision_undo_
absl::Status PreflightDoorMutation(int room_id, const std::vector< zelda3::Room::Door > &doors)
absl::Status RestoreRoomObjects(int room_id, const std::vector< zelda3::RoomObject > &objects, const std::vector< size_t > &selected_indices, const std::vector< chest_data > &chests)
absl::Status PreflightSpriteMutation(int room_id, const std::vector< zelda3::Sprite > &sprites)
absl::Status EditRoomMetadata(int room_id, const RoomMetadataEdit &edit)
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
PendingWaterFillUndo pending_water_fill_undo_
absl::Status Cut() override
std::array< PendingEntityUndo, 3 > pending_entity_undo_
absl::Status Paste() override
static bool IsValidRoomId(int room_id)
void FinalizeUndoAction(int room_id)
DungeonCanvasViewer * GetViewerForRoom(int room_id)
PendingSelectionUndo pending_selection_undo_
absl::StatusOr< std::string > ExportRoomDocument(int room_id)
void BeginWaterFillUndoSnapshot(int room_id)
absl::Status Undo() override
absl::Status RestoreRoomCustomCollisionBatch(const std::vector< DungeonCustomCollisionSnapshot > &snapshots)
void FinalizePendingEntityUndoActions()
absl::Status DeleteChest(int room_id, size_t index)
absl::Status Redo() override
absl::StatusOr< DungeonRoomTransferPlan > PreviewRoomTransfer(int target_room_id, int source_room_id, const std::string &json, const DungeonRoomTransferOptions &options)
void BeginUndoSnapshot(int room_id)
zelda3::GameData * game_data_
DungeonSelectionClipboard selection_clipboard_
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
void RestoreRoomCustomCollision(int room_id, const zelda3::CustomCollisionMap &map)
bool undo_restore_triggered_ping_
absl::Status EditChest(int room_id, size_t index, uint8_t item_id, bool big_chest)
int current_room_id() const
absl::Status ApplyMinecartCollisionBatch(const std::vector< zelda3::TrackCollisionResult > &preview, const zelda3::GeneratorOptions &options)
void FinalizeCollisionUndoAction(int room_id)
absl::StatusOr< DungeonConnectionPlan > PreviewDoorConnection(const DungeonConnectionRequest &request)
void SetCacheInvalidationCallback(std::function< void()> callback)
void SetSelectionEditCallbacks(std::function< absl::Status(const DungeonSelectionEditPlan &, bool)> edit, std::function< void()> finish, std::function< void(const absl::Status &)> error={})
MutationDomain last_invalidation_domain() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SetMutationCallback(std::function< void()> callback)
void SetSharedSelectionClipboard(DungeonSelectionClipboard *clipboard)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
void ForEachMaterialized(Fn &&fn)
EditorContext context() const
UndoManager undo_manager_
EditorDependencies dependencies_
ItemInteractionHandler & item_handler()
SpriteInteractionHandler & sprite_handler()
TileObjectHandler & tile_handler()
DoorInteractionHandler & door_handler()
void SetItemPlacementPreflight(ItemPlacementPreflight preflight)
void InvalidateRoomAudit()
void SetSpriteMutationPreflight(SpriteMutationPreflight callback)
void SetObjectMutationPreflight(ObjectMutationPreflight callback)
void SetMutationErrorCallback(std::function< void(const absl::Status &)> callback)
void SetBeforeObjectCommandCallback(std::function< void()> callback)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
Abstract base for all undoable actions (Command pattern)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
std::string GetRedoDescription() const
Description of the action that would be redone (for UI)
std::string GetUndoDescription() const
Description of the action that would be undone (for UI)
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
~CollisionBatchRollback()
std::vector< CollisionRollbackEntry > entries_
CollisionBatchRollback(std::vector< CollisionRollbackEntry > entries)
absl::Status Redo() override
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
RoomGraphicsSheetAction(int group, int slot, uint8_t before, uint8_t after, Apply apply)
absl::Status Undo() override
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::function< absl::Status(uint8_t, uint8_t)> Apply
bool HasSheet(uint16_t sheet) const
True when the buffer holds all 4096 bytes of sheet.
static constexpr uint16_t kSheetCount
const CustomCollisionMap & custom_collision() const
bool custom_collision_dirty() const
const WaterFillZoneMap & water_fill_zone() const
const std::vector< Door > & GetDoors() const
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< PotItem > & GetPotItems() const
uint8_t water_fill_sram_bit_mask() const
Locates graphics sheets in the ROM and writes edited sheets back.
#define LOG_ERROR(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
bool IsEntityMutation(MutationDomain domain)
std::vector< SelectedEntity > ValidEntitySelection(const zelda3::Room &room, const std::vector< SelectedEntity > &selected)
size_t EntityDomainIndex(MutationDomain domain)
bool SameEntityData(const DungeonEntitySnapshot &a, const DungeonEntitySnapshot &b)
bool CollisionMapsEqual(const zelda3::CustomCollisionMap &lhs, const zelda3::CustomCollisionMap &rhs)
bool HasAnyCustomCollision(const zelda3::CustomCollisionMap &map)
WaterFillSnapshot MakeWaterFillSnapshot(const zelda3::Room &room)
Editors are the view controllers for the application.
MutationDomain
Domain/type of mutation for undo + invalidation routing.
absl::StatusOr< zelda3::CustomCollisionMap > PreviewMinecartCollisionStamp(const zelda3::CustomCollisionMap &before, const MinecartCollisionStamp &stamp)
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
constexpr bool HasCustomCollisionWriteSupport(std::size_t rom_size)
absl::StatusOr< TrackCollisionResult > GenerateTrackCollision(Room *room, const GeneratorOptions &options)
#define RETURN_IF_ERROR(expr)
zelda3::CustomCollisionMap before
std::optional< DungeonSelectionEditPlan > plan
std::vector< size_t > before_selection
std::vector< chest_data > before_chests
std::vector< zelda3::RoomObject > before_objects
std::vector< DungeonSpriteSnapshot > sprites
std::vector< size_t > objects
std::vector< zelda3::PotItem > items
std::vector< SelectedEntity > entities
std::vector< zelda3::Room::Door > doors
ToastManager * toast_manager
std::vector< uint16_t > offsets
zelda3::CustomCollisionMap map
bool HasOverrides() const
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
GraphicsSheetStore sheet_store