19 using Restore = std::function<absl::Status(
20 const std::vector<DungeonSelectionEditPlan>&,
bool)>;
22 std::string description,
Restore restore)
23 : plans_(std::move(plans)),
24 description_(std::move(description)),
25 restore_(std::move(restore)) {}
26 absl::Status
Undo()
override {
return restore_(plans_,
false); }
27 absl::Status
Redo()
override {
return restore_(plans_,
true); }
28 std::string
Description()
const override {
return description_; }
36 state.selected_objects.size() *
sizeof(size_t) +
39 size_t total = description_.capacity();
40 for (
const auto& plan : plans_) {
41 total += bytes(plan.before) + bytes(plan.after);
47 std::vector<DungeonSelectionEditPlan>
plans_;
55 int room_id,
const std::vector<zelda3::Room::Door>& doors) {
57 if (!room || room->rom() !=
rom_) {
58 return absl::FailedPreconditionError(
"Door room is not loaded");
61 candidate.doors = doors;
66 int room_id,
const std::vector<zelda3::Sprite>& sprites) {
68 if (!room || room->rom() !=
rom_) {
69 return absl::FailedPreconditionError(
"Sprite room is not loaded");
72 candidate.sprites.clear();
73 for (
const auto& sprite : sprites) {
74 candidate.sprites.push_back({sprite.id(), sprite.x(), sprite.y(),
75 sprite.subtype(), sprite.layer(),
76 sprite.key_drop(), sprite.deleted()});
84 if (!original || original->rom() !=
rom_) {
85 return absl::FailedPreconditionError(
"Stream room is not loaded");
87 constexpr uint8_t streams =
89 if (!(domains & streams)) {
90 return absl::OkStatus();
98 const bool objects_changed =
101 const bool sprites_changed =
104 if (!objects_changed && !sprites_changed) {
105 return absl::OkStatus();
113 edit.
after = candidate;
120 "dungeon stream authoring",
"DungeonEditorV2",
nullptr));
122 return absl::OkStatus();
125absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
127 const std::vector<DungeonSelectionEditPlan>& candidates)
const {
129 std::set<int> candidate_rooms;
130 for (
const auto& candidate : candidates) {
131 if (!candidate.domains)
134 if (!room || room->rom() !=
rom_ ||
135 !candidate_rooms.insert(candidate.room_id).second) {
136 return absl::FailedPreconditionError(
137 "Stream candidates must identify distinct loaded rooms");
139 domains |= candidate.domains;
141 constexpr uint8_t streams =
144 const bool objects_enabled =
146 const bool sprites_enabled =
155 std::vector<std::pair<uint32_t, uint32_t>> writes;
156 if (!objects_pending && !sprites_pending)
159 return absl::FailedPreconditionError(
"ROM is not loaded");
160 std::optional<zelda3::DungeonStreamLayout> object_layout, sprite_layout;
163 auto* result) -> absl::Status {
165 return absl::OkStatus();
172 return absl::OkStatus();
175 objects_pending, &object_layout));
177 sprites_pending, &sprite_layout));
178 const auto append = [&](
const auto& ranges) {
179 writes.insert(writes.end(), ranges.begin(), ranges.end());
189 const auto check_room = [&]() -> absl::Status {
191 const auto candidate = std::find_if(
192 candidates.begin(), candidates.end(), [
id](
const auto& edit) {
193 return edit.room_id == id && edit.domains;
195 const bool has_candidate = candidate != candidates.end();
197 dirty.object_stream |=
201 if (!(objects_enabled &&
202 (dirty.object_stream || dirty.object_stream_header)) &&
203 !(sprites_enabled && dirty.sprites))
204 return absl::OkStatus();
213 const auto* objects = object_layout ? &*object_layout :
nullptr;
214 const auto* sprites = sprite_layout ? &*sprite_layout :
nullptr;
215 if (objects_enabled && dirty.object_stream) {
220 if (objects_enabled && dirty.object_stream_header) {
226 if (sprites_enabled && dirty.sprites) {
231 return absl::OkStatus();
233 status = check_room();
242 if (!room || room->rom() !=
rom_) {
243 return absl::FailedPreconditionError(
"Selection room is not loaded");
246 return absl::OkStatus();
256 return absl::FailedPreconditionError(
257 "Finish the current selection drag before editing another room");
279 return absl::FailedPreconditionError(
280 "Selection changed while finishing the previous gesture; repeat the "
293 pending.after = plan.
after;
294 pending.domains |= plan.
domains;
299 return absl::OkStatus();
303 const auto verb = [&]() {
316 const auto description =
317 absl::StrFormat(
"%s room %03X selection", verb, plan.
room_id);
322 std::vector<DungeonSelectionEditPlan> plans, std::string description) {
324 std::move(plans), std::move(description),
325 [
this](
const std::vector<DungeonSelectionEditPlan>& states,
bool after) {
336 auto plan = std::move(*pending.plan);
338 if (!plan.changed()) {
343 room && room->rom() ==
rom_) {
344 room->RestoreSaveDirtySnapshot(pending.dirty_before);
352 const std::vector<DungeonSelectionEditPlan>& plans,
bool after) {
355 for (
const auto& plan : plans) {
357 if (!room || room->rom() !=
rom_ || room->id() != plan.room_id) {
358 return absl::FailedPreconditionError(
"Selection undo room is not loaded");
361 for (
const auto& plan : plans) {
363 after ? plan.after : plan.before,
366 for (
const auto& plan : plans) {
370 return absl::OkStatus();
380 room->RenderRoomGraphics();
386 viewer->InvalidateConnectedRoomGraph();
387 if (viewer->current_room_id() != room_id)
390 auto& interaction = viewer->object_interaction();
391 interaction.CancelPlacement();
393 interaction.entity_coordinator().SetSelectedEntities(
396 viewer->TriggerChangePing();
398 room_viewers_.ForEach([&](
int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
399 refresh(viewer.get());
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const DungeonStreamLayout * GetDungeonStreamLayout(DungeonStreamType stream) const
Get an explicitly declared dungeon stream layout.
bool loaded() const
Check if the manifest has been loaded.
class MinecartTrackEditorPanel * minecart_track_editor_panel_
absl::Status PreflightObjectMutation(int room_id, const std::vector< zelda3::RoomObject > &objects, const std::vector< chest_data > &chests, bool check_streams=true)
PendingUndo pending_undo_
void FinalizeSelectionUndoAction()
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
absl::Status CommitSelectionEdit(const DungeonSelectionEditPlan &plan, bool continuous)
void FinalizeWaterFillUndoAction(int room_id)
void RefreshSelectionEditViews(int room_id, const DungeonSelectionEditState *selection)
void FinalizePendingUndoActions()
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > PlanPendingRoomStreamRanges(const std::vector< DungeonSelectionEditPlan > &candidates) const
PendingCollisionUndo pending_collision_undo_
absl::Status PreflightDoorMutation(int room_id, const std::vector< zelda3::Room::Door > &doors)
absl::Status PreflightSpriteMutation(int room_id, const std::vector< zelda3::Sprite > &sprites)
absl::Status RestoreSelectionEditBatch(const std::vector< DungeonSelectionEditPlan > &plans, bool after)
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
PendingWaterFillUndo pending_water_fill_undo_
void FinalizeUndoAction(int room_id)
PendingSelectionUndo pending_selection_undo_
void FinalizePendingEntityUndoActions()
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
bool undo_restore_triggered_ping_
absl::Status PreflightStreamMutation(int room_id, const DungeonSelectionEditState &candidate, uint8_t domains)
void FinalizeCollisionUndoAction(int room_id)
void PushSelectionUndoBatch(std::vector< DungeonSelectionEditPlan > plans, std::string description)
void PushSelectionUndoAction(DungeonSelectionEditPlan plan)
zelda3::Room * GetIfLoaded(int room_id)
void ForEachLoaded(Fn &&fn)
UndoManager undo_manager_
project::YazeProject * project() const
EditorDependencies dependencies_
void InvalidateRoomAudit()
Abstract base for all undoable actions (Command pattern)
void Push(std::unique_ptr< UndoAction > action)
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
DungeonSelectionAction(std::vector< DungeonSelectionEditPlan > plans, std::string description, Restore restore)
std::function< absl::Status( const std::vector< DungeonSelectionEditPlan > &, bool)> Restore
std::vector< DungeonSelectionEditPlan > plans_
absl::Status Redo() override
absl::Status Undo() override
void RestoreMetadataSnapshot(const MetadataSnapshot &snapshot)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetObjectSaveWriteRanges(const DungeonStreamLayout *layout=nullptr) const
bool object_stream_dirty() const
bool sprites_dirty() const
bool object_stream_header_dirty() const
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetObjectStreamHeaderSaveWriteRanges(const DungeonStreamLayout *layout=nullptr) const
absl::Status SaveObjects(const DungeonStreamLayout *layout=nullptr)
void RestoreSaveDirtySnapshot(const SaveDirtySnapshot &snapshot)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetSpriteSaveWriteRanges(const DungeonStreamLayout *layout=nullptr) const
std::vector< uint8_t > EncodeObjects() const
absl::Status SaveObjectStreamHeader(const DungeonStreamLayout *layout=nullptr)
absl::Status SaveSprites(const DungeonStreamLayout *layout=nullptr)
std::vector< uint8_t > EncodeSprites() const
SaveDirtySnapshot CaptureSaveDirtySnapshot() const
MetadataSnapshot CaptureMetadataSnapshot() const
struct chest_data chest_data
Legacy chest data structure.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
DungeonStreamType
Dungeon stream kinds with allocator layouts in the hack manifest.
absl::StatusOr< zelda3::DungeonStreamLayout > ToDungeonStreamAllocatorLayout(DungeonStreamType stream_type, const DungeonStreamLayout &manifest_layout)
Editors are the view controllers for the application.
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
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)
#define RETURN_IF_ERROR(expr)
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
zelda3::Room::SaveDirtySnapshot dirty_before
std::optional< DungeonSelectionEditPlan > plan
DungeonSelectionEditKind kind
DungeonSelectionEditState before
DungeonSelectionEditState after
std::vector< SelectedEntity > selected_entities
std::vector< zelda3::RoomObject > objects
std::vector< chest_data > chests
std::vector< size_t > selected_objects
project::YazeProject * project
Represents a selected entity in the dungeon editor.
core::HackManifest hack_manifest
Represents a door in a dungeon room.