yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_undo_actions.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
2#define YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
3
4#include <chrono>
5#include <cstddef>
6#include <functional>
7#include <optional>
8#include <string>
9#include <unordered_map>
10#include <utility>
11#include <vector>
12
13#include "absl/status/status.h"
14#include "absl/strings/str_format.h"
18
19namespace yaze {
20namespace editor {
21
26 int x = 0;
27 int y = 0;
28 int old_tile_id = 0;
29 int new_tile_id = 0;
30};
31
44 public:
46 static constexpr int kMergeWindowMs = 500;
47
56 std::vector<OverworldTileChange> tile_changes,
57 zelda3::Overworld* overworld,
58 std::function<void()> refresh_fn)
59 : map_id_(map_id),
61 tile_changes_(std::move(tile_changes)),
62 overworld_(overworld),
63 refresh_fn_(std::move(refresh_fn)),
64 timestamp_(std::chrono::steady_clock::now()) {}
65
66 absl::Status Undo() override {
67 if (!overworld_) {
68 return absl::InternalError("Overworld pointer is null");
69 }
70 auto& world_tiles = overworld_->GetMapTiles(world_);
71 for (const auto& change : tile_changes_) {
72 world_tiles[change.x][change.y] = change.old_tile_id;
73 }
74 if (refresh_fn_) {
76 }
77 return absl::OkStatus();
78 }
79
80 absl::Status Redo() override {
81 if (!overworld_) {
82 return absl::InternalError("Overworld pointer is null");
83 }
84 auto& world_tiles = overworld_->GetMapTiles(world_);
85 for (const auto& change : tile_changes_) {
86 world_tiles[change.x][change.y] = change.new_tile_id;
87 }
88 if (refresh_fn_) {
90 }
91 return absl::OkStatus();
92 }
93
94 std::string Description() const override {
95 return absl::StrFormat("Paint %d tile%s on map %d", tile_changes_.size(),
96 tile_changes_.size() == 1 ? "" : "s", map_id_);
97 }
98
99 size_t MemoryUsage() const override {
100 return sizeof(*this) + tile_changes_.size() * sizeof(OverworldTileChange);
101 }
102
103 bool CanMergeWith(const UndoAction& prev) const override {
104 const auto* prev_paint =
105 dynamic_cast<const OverworldTilePaintAction*>(&prev);
106 if (!prev_paint)
107 return false;
108 if (prev_paint->map_id_ != map_id_)
109 return false;
110 if (prev_paint->world_ != world_)
111 return false;
112
113 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
114 timestamp_ - prev_paint->timestamp_);
115 return elapsed.count() <= kMergeWindowMs;
116 }
117
118 void MergeWith(UndoAction& prev) override {
119 auto& prev_paint = static_cast<OverworldTilePaintAction&>(prev);
120
121 // Build a map of (x,y) -> index in our tile_changes_ for fast lookup
122 // so we can keep the earliest old_tile_id for coordinates that appear
123 // in both actions.
124 std::unordered_map<int64_t, size_t> coord_index;
125 for (size_t i = 0; i < tile_changes_.size(); ++i) {
126 int64_t key = (static_cast<int64_t>(tile_changes_[i].x) << 32) |
127 static_cast<int64_t>(tile_changes_[i].y);
128 coord_index[key] = i;
129 }
130
131 for (const auto& prev_change : prev_paint.tile_changes_) {
132 int64_t key = (static_cast<int64_t>(prev_change.x) << 32) |
133 static_cast<int64_t>(prev_change.y);
134 auto it = coord_index.find(key);
135 if (it != coord_index.end()) {
136 // Same coordinate exists in both: keep the older old_tile_id
137 tile_changes_[it->second].old_tile_id = prev_change.old_tile_id;
138 } else {
139 // Coordinate only in prev: adopt it as-is
140 tile_changes_.push_back(prev_change);
141 }
142 }
143
144 // Keep the earlier timestamp so subsequent merges measure from
145 // the start of the combined stroke.
146 timestamp_ = prev_paint.timestamp_;
147 }
148
149 int map_id() const { return map_id_; }
150 int world() const { return world_; }
151 const std::vector<OverworldTileChange>& tile_changes() const {
152 return tile_changes_;
153 }
154
155 private:
158 std::vector<OverworldTileChange> tile_changes_;
160 std::function<void()> refresh_fn_; // callback to refresh map visuals
161 std::chrono::steady_clock::time_point timestamp_;
162};
163
170 std::vector<zelda3::OverworldItem> items;
171 std::optional<zelda3::OverworldItem> selected_item_identity;
172};
173
181 public:
182 using RestoreFn = std::function<void(const OverworldItemsSnapshot&)>;
183
185 OverworldItemsSnapshot after, RestoreFn restore,
186 std::string description)
187 : before_(std::move(before)),
188 after_(std::move(after)),
189 restore_(std::move(restore)),
190 description_(std::move(description)) {}
191
192 absl::Status Undo() override {
193 if (!restore_) {
194 return absl::InternalError(
195 "OverworldItemsEditAction: no restore callback");
196 }
198 return absl::OkStatus();
199 }
200
201 absl::Status Redo() override {
202 if (!restore_) {
203 return absl::InternalError(
204 "OverworldItemsEditAction: no restore callback");
205 }
207 return absl::OkStatus();
208 }
209
210 std::string Description() const override { return description_; }
211
212 size_t MemoryUsage() const override {
213 const size_t before_size =
214 before_.items.size() * sizeof(zelda3::OverworldItem);
215 const size_t after_size =
216 after_.items.size() * sizeof(zelda3::OverworldItem);
217 return sizeof(*this) + before_size + after_size;
218 }
219
220 bool CanMergeWith(const UndoAction& /*prev*/) const override { return false; }
221
222 private:
226 std::string description_;
227};
228
229} // namespace editor
230} // namespace yaze
231
232#endif // YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
Undoable action for overworld item mutations.
bool CanMergeWith(const UndoAction &) const override
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
OverworldItemsEditAction(OverworldItemsSnapshot before, OverworldItemsSnapshot after, RestoreFn restore, std::string description)
std::function< void(const OverworldItemsSnapshot &)> RestoreFn
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
Undoable action for painting tiles on the overworld map.
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
void MergeWith(UndoAction &prev) override
bool CanMergeWith(const UndoAction &prev) const override
static constexpr int kMergeWindowMs
Merge window: consecutive paints within this duration become one step.
OverworldTilePaintAction(int map_id, int world, std::vector< OverworldTileChange > tile_changes, zelda3::Overworld *overworld, std::function< void()> refresh_fn)
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
std::vector< OverworldTileChange > tile_changes_
const std::vector< OverworldTileChange > & tile_changes() const
std::chrono::steady_clock::time_point timestamp_
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
Represents the full Overworld data, light and dark world.
Definition overworld.h:261
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:515
Snapshot of overworld item list + current item selection.
std::vector< zelda3::OverworldItem > items
std::optional< zelda3::OverworldItem > selected_item_identity
A single tile coordinate + old/new value pair for undo/redo.