The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
undo_action.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 - 2025
3 by David White <dave@whitevine.net>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
18#include "game_board.hpp"
19#include "log.hpp" // for LOG_STREAM, logger, etc
21#include "resources.hpp"
22#include "variable.hpp" // vconfig
23#include "game_data.hpp"
24#include "units/unit.hpp"
25#include "utils/general.hpp"
26#include "utils/ranges.hpp"
27#include "sound.hpp"
28
29#include <cassert>
30#include <iterator>
31#include <algorithm>
32
33static lg::log_domain log_engine("engine");
34#define ERR_NG LOG_STREAM(err, log_engine)
35#define LOG_NG LOG_STREAM(info, log_engine)
36
37
38namespace actions
39{
40
41
43 : steps_()
44 , unit_id_diff_(0)
45{
46}
47
49{
50 int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
51 for(auto& p_step : steps_ | utils::views::reverse) {
52 p_step->undo(side);
53 }
54 if(last_unit_id - unit_id_diff_ < 0) {
55 ERR_NG << "Next unit id is below 0 after undoing";
56 }
58 return true;
59}
60
62{
63 steps_.emplace_back(std::move(action));
64}
65
66/**
67 * Merges runs of consecutive undo::move_action steps that together form one continuous
68 * path (each one picking up exactly where the previous left off) into a single step
69 * covering the whole stretch.
70 *
71 * move_unit() records one move_action per hex so that, when undoing, any [on_undo] event
72 * handlers interleaved between hexes still fire in the correct order relative to the
73 * unit's position. That splitting serves no purpose across a stretch where nothing was
74 * interleaved, so this collapses those stretches back into one step - which both undoes
75 * (and later animates) as a single continuous motion, and avoids writing one redundant
76 * step per hex to the save file.
77 */
79{
80 for(auto it = steps_.begin(); it != steps_.end(); ++it) {
81 auto* first = dynamic_cast<undo::move_action*>(it->get());
82 if(!first) {
83 continue;
84 }
85
86 auto next = std::next(it);
87 while(next != steps_.end()) {
88 auto* second = dynamic_cast<undo::move_action*>(next->get());
89 if(!second || second->route.front() != first->route.back()) {
90 break;
91 }
92 first->route.insert(first->route.end(), second->route.begin() + 1, second->route.end());
93 next = steps_.erase(next);
94 }
95 }
96}
97
98
100{
101 for(const config& step : cfg.child_range("step")) {
102 if(auto* factory = utils::find(get_factories(), step["type"].str())) {
103 add(factory->second(step));
104 } else {
105 throw config::error("Invalid undo action type: '" + step["type"].str() + "'");
106 }
107 }
108}
110{
111 for(auto& p_step : steps_) {
112 p_step->write(cfg.add_child("step"));
113 }
114}
115
121
122
123
124
125
126
127undo_event::undo_event(int fcn_idx, const config& args, const game_events::queued_event& ctx)
128 : lua_idx(fcn_idx)
129 , commands(args)
130 , data(ctx.data)
131 , loc1(ctx.loc1)
132 , loc2(ctx.loc2)
133 , filter_loc1(ctx.loc1.filter_loc())
134 , filter_loc2(ctx.loc2.filter_loc())
135 , uid1(), uid2()
136{
137 unit_const_ptr u1 = ctx.loc1.get_unit(), u2 = ctx.loc2.get_unit();
138 if(u1) {
139 id1 = u1->id();
140 uid1 = u1->underlying_id();
141 }
142 if(u2) {
143 id2 = u2->id();
144 uid2 = u2->underlying_id();
145 }
146}
147
149 : commands(cmds)
150 , data(ctx.data)
151 , loc1(ctx.loc1)
152 , loc2(ctx.loc2)
153 , filter_loc1(ctx.loc1.filter_loc())
154 , filter_loc2(ctx.loc2.filter_loc())
155 , uid1(), uid2()
156{
157 unit_const_ptr u1 = ctx.loc1.get_unit(), u2 = ctx.loc2.get_unit();
158 if(u1) {
159 id1 = u1->id();
160 uid1 = u1->underlying_id();
161 }
162 if(u2) {
163 id2 = u2->id();
164 uid2 = u2->underlying_id();
165 }
166}
167
168undo_event::undo_event(const config& first, const config& second, const config& weapons, const config& cmds)
169 : commands(cmds)
170 , data(weapons)
171 , loc1(first["x"], first["y"], wml_loc())
172 , loc2(second["x"], second["y"], wml_loc())
173 , filter_loc1(first["filter_x"], first["filter_y"], wml_loc())
174 , filter_loc2(second["filter_x"], second["filter_y"], wml_loc())
175 , uid1(first["underlying_id"].to_size_t())
176 , uid2(second["underlying_id"].to_size_t())
177 , id1(first["id"])
178 , id2(second["id"])
179{
180}
181
183 : undo_event(cfg.child_or_empty("filter"),
184 cfg.child_or_empty("filter_second"),
185 cfg.child_or_empty("data"),
186 cfg.child_or_empty("command"))
187{
188}
189
190
191namespace
192{
193unit_ptr get_unit(std::size_t uid, const std::string& id)
194{
195 assert(resources::gameboard);
196 auto iter = resources::gameboard->units().find(uid);
197 if(!iter.valid() || iter->id() != id) {
198 return nullptr;
199 }
200 return iter.get_shared_ptr();
201}
202} // namespace
203
205{
206 undo_event& e = *this;
207 std::string tag = "undo";
208 assert(resources::lua_kernel);
209 assert(resources::gamedata);
210
215 std::swap(x1, resources::gamedata->get_variable("x1"));
216 std::swap(y1, resources::gamedata->get_variable("y1"));
217 std::swap(x2, resources::gamedata->get_variable("x2"));
218 std::swap(y2, resources::gamedata->get_variable("y2"));
219
220 std::unique_ptr<scoped_xy_unit> u1, u2;
221 if(unit_ptr who = get_unit(e.uid1, e.id1)) {
222 u1.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
223 }
224 if(unit_ptr who = get_unit(e.uid2, e.id2)) {
225 u2.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
226 }
227
228 scoped_weapon_info w1("weapon", e.data.optional_child("first"));
229 scoped_weapon_info w2("second_weapon", e.data.optional_child("second"));
230
231 game_events::queued_event q(tag, "", map_location(x1, y1, wml_loc()), map_location(x2, y2, wml_loc()), e.data);
232 if(e.lua_idx.has_value()) {
233 resources::lua_kernel->run_wml_event(*e.lua_idx, vconfig(e.commands), q);
234 } else {
235 resources::lua_kernel->run_wml_action("command", vconfig(e.commands), q);
236 }
238
239 std::swap(x1, resources::gamedata->get_variable("x1"));
240 std::swap(y1, resources::gamedata->get_variable("y1"));
241 std::swap(x2, resources::gamedata->get_variable("x2"));
242 std::swap(y2, resources::gamedata->get_variable("y2"));
243 return true;
244}
245
247{
249 auto& evt = *this;
250 if(evt.lua_idx.has_value()) {
251 // TODO: Log warning that this cannot be serialized
252 return;
253 }
254 config& entry = cfg;
255 config& first = entry.add_child("filter");
256 config& second = entry.add_child("filter_second");
257 entry.add_child("data", evt.data);
258 entry.add_child("command", evt.commands);
259 // First location
260 first["filter_x"] = evt.filter_loc1.wml_x();
261 first["filter_y"] = evt.filter_loc1.wml_y();
262 first["underlying_id"] = evt.uid1;
263 first["id"] = evt.id1;
264 first["x"] = evt.loc1.wml_x();
265 first["y"] = evt.loc1.wml_y();
266 // Second location
267 second["filter_x"] = evt.filter_loc2.wml_x();
268 second["filter_y"] = evt.filter_loc2.wml_y();
269 second["underlying_id"] = evt.uid2;
270 second["id"] = evt.id2;
271 second["x"] = evt.loc2.wml_x();
272 second["y"] = evt.loc2.wml_y();
273}
274
275
277
278} // namespace actions
#define ERR_NG
std::unique_ptr< undo_action > t_step_ptr
void read(const config &cfg)
Creates the list of undo steps based on a config.
std::map< std::string, t_factory > t_factory_map
void combine_moves()
Merges consecutive undo::move_action steps that form one continuous path into a single step.
void add(t_step_ptr &&action)
static t_factory_map & get_factories()
undo_event(int fcn_idx, const config &args, const game_events::queued_event &ctx)
virtual bool undo(int side)
Undoes this action.
virtual void write(config &cfg) const
Writes this into the provided config.
Variant for storing WML attributes.
static config_attribute_value create(const T val)
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
child_itors child_range(std::string_view key)
Definition config.cpp:268
n_unit::id_manager & unit_id_manager()
virtual const unit_map & units() const override
bool run_wml_event(int ref, const vconfig &args, const game_events::queued_event &ev, bool *out=nullptr)
Run a WML stored in the Lua registry.
bool run_wml_action(const std::string &, const vconfig &, const game_events::queued_event &)
Runs a command from an event handler.
std::size_t get_save_id() const
Used for saving id to savegame.
Definition id.cpp:42
void set_save_id(std::size_t)
Definition id.cpp:47
unit_iterator find(std::size_t id)
Definition map.cpp:302
A variable-expanding proxy for the config class.
Definition variable.hpp:45
const config * cfg
Standard logging facilities (interface).
static auto red_undo_event
game_board * gameboard
Definition resources.cpp:20
game_data * gamedata
Definition resources.cpp:22
game_lua_kernel * lua_kernel
Definition resources.cpp:25
void commit_music_changes()
Definition sound.cpp:795
constexpr auto reverse
Definition ranges.hpp:44
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition general.hpp:142
std::string_view data
Definition picture.cpp:188
std::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
std::shared_ptr< unit > unit_ptr
Definition ptr.hpp:26
route_t route
The hexes occupied by the affected unit during this action.
virtual void write(config &cfg) const
Writes this into the provided config.
unit_const_ptr get_unit() const
entity_location loc1
Definition pump.hpp:65
entity_location loc2
Definition pump.hpp:66
Encapsulates the map of the game.
Definition location.hpp:46
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition map.hpp:217
static lg::log_domain log_engine("engine")
#define e