25 restore_(std::move(restore)) {}
26 absl::Status
Undo()
override {
return restore_(room_id_, index_, before_); }
27 absl::Status
Redo()
override {
return restore_(room_id_, index_, after_); }
29 return absl::StrFormat(
"Edit room %03X chest %d contents", room_id_,
53 const std::vector<zelda3::RoomObject>& after,
55 const auto matches = [option](
const auto& object) {
58 auto a = before.begin();
59 auto b = after.begin();
61 a = std::find_if(a, before.end(), matches);
62 b = std::find_if(b, after.end(), matches);
63 if (a == before.end() || b == after.end()) {
64 return a == before.end() && b == after.end();
66 if (a->x() != b->x() || a->y() != b->y() ||
67 a->GetLayerValue() != b->GetLayerValue()) {
71 if (a->lit_ != b->lit_ ||
72 a->torch_reserved_bit() != b->torch_reserved_bit()) {
75 }
else if (a->block_behavior_layer() != b->block_behavior_layer() ||
76 a->block_load_order() != b->block_load_order()) {
85 std::vector<std::optional<size_t>> origins(count);
86 for (
size_t i = 0; i < count; ++i)
93 uint8_t item_id,
bool big_chest) {
95 if (!room || room->rom() !=
rom_ || !room->AreChestsLoaded()) {
96 return absl::FailedPreconditionError(
"Chest contents are not loaded");
98 if (index >= room->GetChests().size()) {
99 return absl::InvalidArgumentError(
100 "Select an existing chest contents record");
102 const auto before = room->GetChests()[index];
104 if (before.id == after.id && before.size == after.size) {
105 return absl::OkStatus();
107 if (before.size != big_chest) {
108 const auto object_index = ObjectForChest(*room, index);
110 return absl::FailedPreconditionError(
111 "Chest object and contents do not match; review the room before "
112 "changing chest type");
114 auto objects = room->GetTileObjects();
115 auto&
object = objects[*object_index];
116 object.set_id(big_chest ? 0xFB1 : 0xF99);
119 std::vector<std::optional<chest_data>> overrides(objects.size());
120 overrides[*object_index] = after;
123 room->GetTileObjects(), room->GetChests(), objects,
124 ObjectOrigins(objects.size()), overrides));
127 auto chests = room->GetChests();
128 chests[index] = after;
134 room_id, index, before, after,
138 return absl::OkStatus();
143 if (!room || room->rom() !=
rom_ || !room->AreChestsLoaded()) {
144 return absl::FailedPreconditionError(
"Chest contents are not loaded");
146 if (index >= room->GetChests().size()) {
147 return absl::InvalidArgumentError(
"Select an existing chest");
149 const auto object_index = ObjectForChest(*room, index);
151 return absl::FailedPreconditionError(
152 "Chest object and contents do not match; review the room before "
155 auto objects = room->GetTileObjects();
156 auto origins = ObjectOrigins(objects.size());
157 objects.erase(objects.begin() + *object_index);
158 origins.erase(origins.begin() + *object_index);
160 room->GetTileObjects(), room->GetChests(),
166 int room_id,
const std::vector<zelda3::RoomObject>& objects,
167 const std::vector<chest_data>& chests,
bool check_streams) {
169 if (!room || room->rom() !=
rom_) {
170 return absl::FailedPreconditionError(
"Room objects are not loaded");
173 objects.size() > room->GetTileObjects().size()) {
174 return absl::ResourceExhaustedError(
"Room object limit reached");
176 const bool torches_changed = !SameSpecialObjects(
178 const bool blocks_changed = !SameSpecialObjects(
180 if (torches_changed && !room->AreTorchesLoaded()) {
181 return absl::FailedPreconditionError(
182 "Load the room's torch table before editing torches");
184 if (torches_changed || blocks_changed) {
189 if (blocks_changed) {
190 if (!room->AreBlocksLoaded()) {
191 return absl::FailedPreconditionError(
192 "Load the room's block table before editing pushable blocks");
201 const auto lookup = [&](
int id) ->
const zelda3::Room* {
204 std::vector<std::pair<uint32_t, uint32_t>> ranges;
205 if (torches_changed) {
209 ranges.insert(ranges.end(), torch_ranges.begin(), torch_ranges.end());
211 if (blocks_changed) {
215 ranges.insert(ranges.end(), block_ranges.begin(), block_ranges.end());
222 "special object authoring",
"DungeonEditorV2",
nullptr));
227 candidate.objects = objects;
231 const auto& original = room->GetChests();
232 if (original.size() == chests.size() &&
234 original.begin(), original.end(), chests.begin(),
235 [](
auto a,
auto b) { return a.id == b.id && a.size == b.size; })) {
236 return absl::OkStatus();
239 return absl::FailedPreconditionError(
"Chest contents are not loaded");
251 : rooms_.GetIfMaterialized(id);
257 "chest authoring",
"DungeonEditorV2",
nullptr));
259 return absl::OkStatus();
263 int room_id,
const std::vector<zelda3::RoomObject>& objects,
264 const std::vector<chest_data>& chests,
265 const std::vector<size_t>& selection) {
271 return absl::OkStatus();
277 if (!room || room->rom() !=
rom_ || !room->AreChestsLoaded() ||
278 index >= room->GetChests().
size()) {
279 return absl::FailedPreconditionError(
280 "Chest contents record is unavailable");
282 room->GetChests()[index] =
chest;
283 room->MarkChestsDirty();
287 return absl::OkStatus();
bool loaded() const
Check if the manifest has been loaded.
absl::Status PreflightObjectMutation(int room_id, const std::vector< zelda3::RoomObject > &objects, const std::vector< chest_data > &chests, bool check_streams=true)
void FinalizePendingUndoActions()
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)
void FinalizeUndoAction(int room_id)
absl::Status DeleteChest(int room_id, size_t index)
void BeginUndoSnapshot(int room_id)
bool undo_restore_triggered_ping_
absl::Status PreflightStreamMutation(int room_id, const DungeonSelectionEditState &candidate, uint8_t domains)
absl::Status RestoreChest(int room_id, size_t index, chest_data chest)
absl::Status EditChest(int room_id, size_t index, uint8_t item_id, bool big_chest)
absl::Status ApplyChestObjectEdit(int room_id, const std::vector< zelda3::RoomObject > &objects, const std::vector< chest_data > &chests, const std::vector< size_t > &selection)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
UndoManager undo_manager_
EditorDependencies dependencies_
Abstract base for all undoable actions (Command pattern)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Undo() override
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
ChestContentsAction(int room_id, size_t index, chest_data before, chest_data after, Restore restore)
absl::Status Redo() override
std::function< absl::Status(int, size_t, chest_data)> Restore
const std::vector< chest_data > & GetChests() const
const std::vector< RoomObject > & GetTileObjects() const
void SetTileObjects(const std::vector< RoomObject > &objects)
struct chest chest
Treasure chest.
struct chest_data chest_data
Legacy chest data structure.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::optional< size_t > ObjectForChest(const zelda3::Room &room, size_t chest)
bool SameSpecialObjects(const std::vector< zelda3::RoomObject > &before, const std::vector< zelda3::RoomObject > &after, zelda3::ObjectOption option)
std::vector< std::optional< size_t > > ObjectOrigins(size_t count)
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 kSelectionObjects
std::optional< size_t > ChestIndexForObject(const std::vector< RoomObject > &objects, size_t object_index)
absl::StatusOr< ChestSavePlan > BuildChestSavePlan(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
constexpr size_t kMaxTileObjects
uint8_t CanonicalRoomObjectSize(int object_id, uint8_t requested_size)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetTorchSaveWriteRanges(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetBlockSaveWriteRanges(const Rom *rom, int room_count, const std::function< const Room *(int)> &room_lookup)
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)
Legacy chest data structure.
project::YazeProject * project
core::HackManifest hack_manifest