The Battle for Wesnoth  1.19.25+dev
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 
16 #include "actions/undo_action.hpp"
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 
33 static 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 
38 namespace 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 
117 {
118  static t_factory_map res;
119  return res;
120 }
121 
122 
123 
124 
125 
126 
127 undo_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 
168 undo_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 
191 namespace
192 {
193 unit_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
std::unique_ptr< undo_action > t_step_ptr
Definition: undo_action.hpp:32
void read(const config &cfg)
Creates the list of undo steps based on a config.
Definition: undo_action.cpp:99
std::map< std::string, t_factory > t_factory_map
Definition: undo_action.hpp:63
void combine_moves()
Merges consecutive undo::move_action steps that form one continuous path into a single step.
Definition: undo_action.cpp:78
void add(t_step_ptr &&action)
Definition: undo_action.cpp:61
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()
Definition: game_board.hpp:74
virtual const unit_map & units() const override
Definition: game_board.hpp:107
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
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1287
const config * cfg
Standard logging facilities (interface).
static auto red_undo_event
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
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:726
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:141
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.
Definition: undo_action.hpp:96
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 ERR_NG
Definition: undo_action.cpp:34
#define e