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 <string>
8#include <unordered_map>
9#include <utility>
10#include <vector>
11
12#include "absl/status/status.h"
13#include "absl/strings/str_format.h"
16
17namespace yaze {
18namespace editor {
19
24 int x = 0;
25 int y = 0;
26 int old_tile_id = 0;
27 int new_tile_id = 0;
28};
29
42 public:
44 static constexpr int kMergeWindowMs = 500;
45
54 int map_id, int world,
55 std::vector<OverworldTileChange> tile_changes,
56 zelda3::Overworld* overworld,
57 std::function<void()> refresh_fn)
58 : map_id_(map_id),
60 tile_changes_(std::move(tile_changes)),
61 overworld_(overworld),
62 refresh_fn_(std::move(refresh_fn)),
63 timestamp_(std::chrono::steady_clock::now()) {}
64
65 absl::Status Undo() override {
66 if (!overworld_) {
67 return absl::InternalError("Overworld pointer is null");
68 }
69 auto& world_tiles = overworld_->GetMapTiles(world_);
70 for (const auto& change : tile_changes_) {
71 world_tiles[change.x][change.y] = change.old_tile_id;
72 }
73 if (refresh_fn_) {
75 }
76 return absl::OkStatus();
77 }
78
79 absl::Status Redo() override {
80 if (!overworld_) {
81 return absl::InternalError("Overworld pointer is null");
82 }
83 auto& world_tiles = overworld_->GetMapTiles(world_);
84 for (const auto& change : tile_changes_) {
85 world_tiles[change.x][change.y] = change.new_tile_id;
86 }
87 if (refresh_fn_) {
89 }
90 return absl::OkStatus();
91 }
92
93 std::string Description() const override {
94 return absl::StrFormat("Paint %d tile%s on map %d",
95 tile_changes_.size(),
96 tile_changes_.size() == 1 ? "" : "s",
97 map_id_);
98 }
99
100 size_t MemoryUsage() const override {
101 return sizeof(*this) +
102 tile_changes_.size() * sizeof(OverworldTileChange);
103 }
104
105 bool CanMergeWith(const UndoAction& prev) const override {
106 const auto* prev_paint =
107 dynamic_cast<const OverworldTilePaintAction*>(&prev);
108 if (!prev_paint) return false;
109 if (prev_paint->map_id_ != map_id_) return false;
110 if (prev_paint->world_ != world_) return false;
111
112 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
113 timestamp_ - prev_paint->timestamp_);
114 return elapsed.count() <= kMergeWindowMs;
115 }
116
117 void MergeWith(UndoAction& prev) override {
118 auto& prev_paint = static_cast<OverworldTilePaintAction&>(prev);
119
120 // Build a map of (x,y) -> index in our tile_changes_ for fast lookup
121 // so we can keep the earliest old_tile_id for coordinates that appear
122 // in both actions.
123 std::unordered_map<int64_t, size_t> coord_index;
124 for (size_t i = 0; i < tile_changes_.size(); ++i) {
125 int64_t key = (static_cast<int64_t>(tile_changes_[i].x) << 32) |
126 static_cast<int64_t>(tile_changes_[i].y);
127 coord_index[key] = i;
128 }
129
130 for (const auto& prev_change : prev_paint.tile_changes_) {
131 int64_t key = (static_cast<int64_t>(prev_change.x) << 32) |
132 static_cast<int64_t>(prev_change.y);
133 auto it = coord_index.find(key);
134 if (it != coord_index.end()) {
135 // Same coordinate exists in both: keep the older old_tile_id
136 tile_changes_[it->second].old_tile_id = prev_change.old_tile_id;
137 } else {
138 // Coordinate only in prev: adopt it as-is
139 tile_changes_.push_back(prev_change);
140 }
141 }
142
143 // Keep the earlier timestamp so subsequent merges measure from
144 // the start of the combined stroke.
145 timestamp_ = prev_paint.timestamp_;
146 }
147
148 int map_id() const { return map_id_; }
149 int world() const { return world_; }
150 const std::vector<OverworldTileChange>& tile_changes() const {
151 return tile_changes_;
152 }
153
154 private:
157 std::vector<OverworldTileChange> tile_changes_;
159 std::function<void()> refresh_fn_; // callback to refresh map visuals
160 std::chrono::steady_clock::time_point timestamp_;
161};
162
163} // namespace editor
164} // namespace yaze
165
166#endif // YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
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:514
A single tile coordinate + old/new value pair for undo/redo.