yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_editor_v2_entrance_camera.cc
Go to the documentation of this file.
2
3#include <functional>
4#include <memory>
5#include <tuple>
6#include <utility>
7
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_join.h"
13#include "imgui/imgui_internal.h"
14#include "util/macro.h"
15
16namespace yaze::editor {
17
18namespace {
19
21 const zelda3::RoomEntrance& b) {
22 auto a_camera = CaptureDungeonEntranceCamera(a);
23 auto b_camera = CaptureDungeonEntranceCamera(b);
24 a_camera.dirty = b_camera.dirty = false;
25 return a_camera == b_camera &&
26 std::tie(a.entrance_id_, a.blockset_, a.floor_, a.dungeon_id_,
27 a.ladder_bg_, a.scrolling_, a.exit_, a.music_, a.door_) ==
28 std::tie(b.entrance_id_, b.blockset_, b.floor_, b.dungeon_id_,
29 b.ladder_bg_, b.scrolling_, b.exit_, b.music_, b.door_);
30}
31
34 auto a_camera = CaptureDungeonSpawnCamera(a);
35 auto b_camera = CaptureDungeonSpawnCamera(b);
36 a_camera.dirty = b_camera.dirty = false;
37 return a.spawn_id() == b.spawn_id() && a_camera == b_camera &&
38 std::tie(a.entrance_id, a.main_gfx, a.floor, a.dungeon_id, a.layer,
40 a.song) == std::tie(b.entrance_id, b.main_gfx, b.floor,
41 b.dungeon_id, b.layer,
44}
45
46template <typename Record>
48 public:
49 using ApplyCallback = std::function<absl::Status(const Record&)>;
50
51 DungeonEntrancePropertiesAction(int slot_index, const Record& before,
52 const Record& after, ApplyCallback apply,
53 std::shared_ptr<const int> gesture,
54 uint64_t save_generation)
55 : slot_index_(slot_index),
56 before_(before),
57 after_(after),
58 apply_(std::move(apply)),
59 gesture_(std::move(gesture)),
60 save_generation_(save_generation) {}
61
62 absl::Status Undo() override { return apply_(before_); }
63 absl::Status Redo() override { return apply_(after_); }
64 std::string Description() const override {
65 return absl::StrFormat("Edit entrance properties 0x%02X", slot_index_);
66 }
67 size_t MemoryUsage() const override {
68 return sizeof(*this) + Description().size();
69 }
70 bool CanMergeWith(const UndoAction& previous) const override {
71 const auto* prev =
72 dynamic_cast<const DungeonEntrancePropertiesAction*>(&previous);
73 // A successful save starts a new undo step even if the field stays active
74 // or the gesture happened to return the record to its saved value.
75 return prev && gesture_ && gesture_ == prev->gesture_ &&
76 save_generation_ == prev->save_generation_ &&
77 slot_index_ == prev->slot_index_ &&
78 before_.dirty() == prev->after_.dirty() &&
79 SameProperties(before_, prev->after_);
80 }
81 void MergeWith(UndoAction& previous) override {
82 before_ = static_cast<DungeonEntrancePropertiesAction&>(previous).before_;
83 }
84
85 private:
87 Record before_;
88 Record after_;
90 std::shared_ptr<const int> gesture_;
92};
93
95 public:
97 std::function<absl::Status(const DungeonEntranceCameraState&)>;
98
102 ApplyCallback apply)
103 : slot_index_(slot_index),
104 before_(std::move(before)),
105 after_(std::move(after)),
106 apply_(std::move(apply)) {}
107
108 absl::Status Undo() override { return apply_(before_); }
109 absl::Status Redo() override { return apply_(after_); }
110 std::string Description() const override {
111 return absl::StrFormat("Repair entrance camera 0x%02X", slot_index_);
112 }
113 size_t MemoryUsage() const override {
114 return sizeof(*this) + Description().size();
115 }
116
117 private:
122};
123
124} // namespace
125
127 const ImGuiID active_id = ImGui::GetActiveID();
128 if (active_id != property_edit_active_id_) {
129 property_edit_active_id_ = active_id;
131 active_id != 0 ? std::make_shared<const int>(0) : nullptr;
132 }
133}
134
136 const char* widget_label) {
137 // InputHex groups have no ID while inactive; register their real text input
138 // ID with the group bounds so automation can find them before the first click.
139 ImGui::PushID(field_label);
140 const ImGuiID input_id = ImGui::GetID("");
141 ImGui::PopID();
142 const auto& item = ImGui::GetCurrentContext()->LastItemData;
144 metadata.label = widget_label;
145 metadata.visible = ImGui::IsItemVisible();
146 metadata.enabled = (item.ItemFlags & ImGuiItemFlags_Disabled) == 0;
148 item.Rect.Min.x, item.Rect.Min.y, item.Rect.Max.x, item.Rect.Max.y, true};
150 "Dungeon/Entrances/input:" +
152 "input", input_id, "", metadata);
153}
154
156 DungeonEntrancesPanel* panel) {
158 [this, panel](int slot, const zelda3::RoomEntrance& before,
159 const zelda3::RoomEntrance& after) {
160 RecordEntrancePropertyEdit(slot, before, after,
161 panel->property_edit_gesture());
162 },
163 [this, panel](int slot, const zelda3::DungeonSpawnPoint& before,
164 const zelda3::DungeonSpawnPoint& after) {
165 RecordSpawnPropertyEdit(slot, before, after,
166 panel->property_edit_gesture());
167 });
168 panel->SetCameraTools(
169 [this](int slot) { return GetEntranceCameraState(slot); },
170 [this](int slot) { return RepairEntranceCamera(slot); },
171 [this]() { RefreshEntranceCameraOverlays(); });
172}
173
175 int slot_index, const zelda3::RoomEntrance& before,
176 const zelda3::RoomEntrance& after, std::shared_ptr<const int> gesture) {
177 if (!CanEditDungeonEntrance(slot_index, before) ||
178 !CanEditDungeonEntrance(slot_index, after) ||
179 SameProperties(before, after)) {
180 return;
181 }
183 RefreshEntranceDirtyState(slot_index);
185 std::make_unique<DungeonEntrancePropertiesAction<zelda3::RoomEntrance>>(
186 slot_index, before, after,
187 [this, slot_index](const zelda3::RoomEntrance& record) {
188 return RestoreEntranceProperties(slot_index, record);
189 },
190 std::move(gesture), entrance_property_save_generation_));
191}
192
194 int slot_index, const zelda3::DungeonSpawnPoint& before,
195 const zelda3::DungeonSpawnPoint& after,
196 std::shared_ptr<const int> gesture) {
197 if (!CanEditDungeonSpawnPoint(slot_index, before) ||
198 !CanEditDungeonSpawnPoint(slot_index, after) ||
199 SameProperties(before, after)) {
200 return;
201 }
203 RefreshEntranceDirtyState(slot_index);
205 std::make_unique<
206 DungeonEntrancePropertiesAction<zelda3::DungeonSpawnPoint>>(
207 slot_index, before, after,
208 [this, slot_index](const zelda3::DungeonSpawnPoint& record) {
209 return RestoreSpawnProperties(slot_index, record);
210 },
211 std::move(gesture), entrance_property_save_generation_));
212}
213
215 int slot_index, const zelda3::RoomEntrance& record) {
216 if (!CanEditDungeonEntrance(slot_index, record)) {
217 return absl::OutOfRangeError("Dungeon entrance slot is out of range");
218 }
219 auto& entrance = entrances_[slot_index];
220 if (!CanEditDungeonEntrance(slot_index, entrance) ||
221 entrance.entrance_id_ != record.entrance_id_) {
222 return absl::FailedPreconditionError("Dungeon entrance is mismatched");
223 }
224 entrance = record;
225 RefreshEntranceDirtyState(slot_index);
227 return absl::OkStatus();
228}
229
231 int slot_index, const zelda3::DungeonSpawnPoint& record) {
232 if (!CanEditDungeonSpawnPoint(slot_index, record)) {
233 return absl::OutOfRangeError("Dungeon spawn slot is out of range");
234 }
235 auto& spawn = spawn_points_[slot_index];
236 if (!CanEditDungeonSpawnPoint(slot_index, spawn)) {
237 return absl::FailedPreconditionError("Dungeon spawn point is unavailable");
238 }
239 spawn = record;
240 RefreshEntranceDirtyState(slot_index);
242 return absl::OkStatus();
243}
244
246 // Saving keeps history but clears dirty flags. Compare against the current
247 // ROM, rather than restoring a flag captured before that save.
248 if (slot_index < zelda3::kNumDungeonSpawnPoints) {
249 auto& spawn = spawn_points_[slot_index];
250 if (rom_ != nullptr && rom_->is_loaded()) {
251 const auto saved = zelda3::DungeonSpawnPoint::Load(*rom_, slot_index);
252 if (saved.ok() && SameProperties(spawn, *saved)) {
253 spawn.ClearDirty();
254 return;
255 }
256 }
257 spawn.MarkDirty();
258 } else {
259 auto& entrance = entrances_[slot_index];
260 if (rom_ != nullptr && rom_->is_loaded()) {
261 const zelda3::RoomEntrance saved(
262 rom_, slot_index - zelda3::kNumDungeonSpawnPoints, false);
263 if (saved.load_status().ok() && SameProperties(entrance, saved)) {
264 entrance.ClearDirty();
265 return;
266 }
267 }
268 entrance.MarkDirty();
269 }
270}
271
272std::optional<DungeonEntranceCameraState>
274 if (slot_index < 0 || slot_index >= static_cast<int>(entrances_.size())) {
275 return std::nullopt;
276 }
277 if (slot_index < zelda3::kNumDungeonSpawnPoints) {
278 const auto& spawn = spawn_points_[slot_index];
279 if (!CanEditDungeonSpawnPoint(slot_index, spawn)) {
280 return std::nullopt;
281 }
282 return CaptureDungeonSpawnCamera(spawn);
283 }
284 const auto& entrance = entrances_[slot_index];
285 if (!CanEditDungeonEntrance(slot_index, entrance)) {
286 return std::nullopt;
287 }
288 return CaptureDungeonEntranceCamera(entrance);
289}
290
292 int slot_index, const DungeonEntranceCameraState& state) {
293 if (slot_index < 0 || slot_index >= static_cast<int>(entrances_.size())) {
294 return absl::OutOfRangeError("Dungeon entrance slot is out of range");
295 }
296 if (slot_index < zelda3::kNumDungeonSpawnPoints) {
297 auto& spawn = spawn_points_[slot_index];
298 if (!CanEditDungeonSpawnPoint(slot_index, spawn) ||
300 return absl::FailedPreconditionError(
301 "Dungeon spawn point is unavailable or mismatched");
302 }
303 ApplyDungeonSpawnCameraState(state, &spawn);
304 } else {
305 auto& entrance = entrances_[slot_index];
306 if (!CanEditDungeonEntrance(slot_index, entrance) ||
308 return absl::FailedPreconditionError(
309 "Dungeon entrance is unavailable or mismatched");
310 }
311 ApplyDungeonEntranceCameraState(state, &entrance);
312 }
313 RefreshEntranceDirtyState(slot_index);
315 return absl::OkStatus();
316}
317
319 room_viewers_.ForEach(
320 [this](int room_id, std::unique_ptr<DungeonCanvasViewer>& viewer) {
321 ConfigureViewerRenderContext(viewer.get(), room_id);
322 });
323 if (workbench_viewer_ != nullptr) {
325 }
326 if (workbench_compare_viewer_ != nullptr) {
328 workbench_compare_viewer_->current_room_id());
329 }
330}
331
332absl::Status DungeonEditorV2::RepairEntranceCamera(int slot_index) {
333 const auto before = GetEntranceCameraState(slot_index);
334 if (!before.has_value()) {
335 return absl::FailedPreconditionError(
336 "Dungeon entrance camera data is unavailable");
337 }
338 const auto validation = ValidateDungeonEntranceCamera(*before);
339 if (!validation.repair_source_valid) {
340 return absl::InvalidArgumentError(absl::StrJoin(validation.errors, " "));
341 }
342 if (validation.matches_derived()) {
343 return absl::FailedPreconditionError(
344 "Camera data already matches the derived values");
345 }
346
349 DungeonEntranceCameraState comparison = after;
350 comparison.dirty = before->dirty;
351 if (comparison == *before) {
352 return absl::FailedPreconditionError(
353 "No safely repairable camera fields differ");
354 }
355 after.dirty = true;
356
359 undo_manager_.Push(std::make_unique<DungeonEntranceCameraRepairAction>(
360 slot_index, *before, after,
361 [this, slot_index](const DungeonEntranceCameraState& state) {
362 return RestoreEntranceCameraState(slot_index, state);
363 }));
364 return absl::OkStatus();
365}
366
367} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:187
absl::Status RestoreEntranceCameraState(int slot_index, const DungeonEntranceCameraState &state)
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > entrances_
std::optional< DungeonEntranceCameraState > GetEntranceCameraState(int slot_index) const
absl::Status RestoreEntranceProperties(int slot_index, const zelda3::RoomEntrance &entrance)
void ConfigureEntrancePropertyEditCallbacks(DungeonEntrancesPanel *panel)
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
void RecordEntrancePropertyEdit(int slot_index, const zelda3::RoomEntrance &before, const zelda3::RoomEntrance &after, std::shared_ptr< const int > gesture)
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > spawn_points_
void ConfigureViewerRenderContext(DungeonCanvasViewer *viewer, int room_id)
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
void RecordSpawnPropertyEdit(int slot_index, const zelda3::DungeonSpawnPoint &before, const zelda3::DungeonSpawnPoint &after, std::shared_ptr< const int > gesture)
absl::Status RestoreSpawnProperties(int slot_index, const zelda3::DungeonSpawnPoint &spawn)
WindowContent for displaying and editing dungeon entrances.
void SetPropertyEditCallbacks(EntrancePropertyEditCallback entrance_callback, SpawnPropertyEditCallback spawn_callback)
void SetCameraTools(CameraStateProvider state_provider, CameraRepairCallback repair_callback, std::function< void()> data_changed_callback={})
void RegisterPropertyInput(const char *field_label, const char *widget_label)
std::shared_ptr< const int > property_edit_gesture() const
std::shared_ptr< const int > property_edit_gesture_
UndoManager undo_manager_
Definition editor.h:371
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
void Push(std::unique_ptr< UndoAction > action)
DungeonEntranceCameraRepairAction(int slot_index, DungeonEntranceCameraState before, DungeonEntranceCameraState after, ApplyCallback apply)
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
DungeonEntrancePropertiesAction(int slot_index, const Record &before, const Record &after, ApplyCallback apply, std::shared_ptr< const int > gesture, uint64_t save_generation)
static std::string NormalizeLabel(absl::string_view label)
void RegisterWidget(const std::string &full_path, const std::string &type, ImGuiID imgui_id, const std::string &description="", const WidgetMetadata &metadata=WidgetMetadata())
static WidgetIdRegistry & Instance()
static absl::StatusOr< DungeonSpawnPoint > Load(const Rom &rom, int spawn_id)
Dungeon Room Entrance or Spawn Point.
const absl::Status & load_status() const
bool SameProperties(const zelda3::RoomEntrance &a, const zelda3::RoomEntrance &b)
Editors are the view controllers for the application.
DungeonEntranceCameraState CaptureDungeonSpawnCamera(const zelda3::DungeonSpawnPoint &spawn)
bool CanEditDungeonEntrance(int slot_index, const zelda3::RoomEntrance &entrance)
DungeonEntranceCameraState BuildRepairedDungeonEntranceCamera(const DungeonEntranceCameraState &state)
bool CanEditDungeonSpawnPoint(int slot_index, const zelda3::DungeonSpawnPoint &spawn)
void ApplyDungeonSpawnCameraState(const DungeonEntranceCameraState &state, zelda3::DungeonSpawnPoint *spawn)
DungeonEntranceCameraValidation ValidateDungeonEntranceCamera(const DungeonEntranceCameraState &state)
DungeonEntranceCameraState CaptureDungeonEntranceCamera(const zelda3::RoomEntrance &entrance)
void ApplyDungeonEntranceCameraState(const DungeonEntranceCameraState &state, zelda3::RoomEntrance *entrance)
constexpr int kNumDungeonSpawnPoints
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22