The Battle for Wesnoth  1.19.8+dev
undo_action.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 - 2024
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"
17 #include "game_board.hpp"
18 #include "log.hpp" // for LOG_STREAM, logger, etc
20 #include "resources.hpp"
21 #include "variable.hpp" // vconfig
22 #include "game_data.hpp"
23 #include "units/unit.hpp"
24 #include "utils/general.hpp"
25 #include "utils/ranges.hpp"
26 #include "sound.hpp"
27 
28 #include <cassert>
29 #include <iterator>
30 #include <algorithm>
31 
32 static lg::log_domain log_engine("engine");
33 #define ERR_NG LOG_STREAM(err, log_engine)
34 #define LOG_NG LOG_STREAM(info, log_engine)
35 
36 
37 namespace actions
38 {
39 
40 
42  : steps_()
43  , unit_id_diff_(0)
44 {
45 }
46 
48 {
49  int last_unit_id = resources::gameboard->unit_id_manager().get_save_id();
50  for(auto& p_step : steps_ | utils::views::reverse) {
51  p_step->undo(side);
52  }
53  if(last_unit_id - unit_id_diff_ < 0) {
54  ERR_NG << "Next unit id is below 0 after undoing";
55  }
57  return true;
58 }
59 
61 {
62  steps_.emplace_back(std::move(action));
63 }
64 
65 
67 {
68  for(const config& step : cfg.child_range("step")) {
69  if(auto* factory = utils::find(get_factories(), step["type"].str())) {
70  add(factory->second(step));
71  } else {
72  throw config::error("Invalid undo action type: '" + step["type"].str() + "'");
73  }
74  }
75 }
77 {
78  for(auto& p_step : steps_) {
79  p_step->write(cfg.add_child("step"));
80  }
81 }
82 
84 {
85  static t_factory_map res;
86  return res;
87 }
88 
89 
90 
91 
92 
93 
94 undo_event::undo_event(int fcn_idx, const config& args, const game_events::queued_event& ctx)
95  : lua_idx(fcn_idx)
96  , commands(args)
97  , data(ctx.data)
98  , loc1(ctx.loc1)
99  , loc2(ctx.loc2)
100  , filter_loc1(ctx.loc1.filter_loc())
101  , filter_loc2(ctx.loc2.filter_loc())
102  , uid1(), uid2()
103 {
104  unit_const_ptr u1 = ctx.loc1.get_unit(), u2 = ctx.loc2.get_unit();
105  if(u1) {
106  id1 = u1->id();
107  uid1 = u1->underlying_id();
108  }
109  if(u2) {
110  id2 = u2->id();
111  uid2 = u2->underlying_id();
112  }
113 }
114 
116  : commands(cmds)
117  , data(ctx.data)
118  , loc1(ctx.loc1)
119  , loc2(ctx.loc2)
120  , filter_loc1(ctx.loc1.filter_loc())
121  , filter_loc2(ctx.loc2.filter_loc())
122  , uid1(), uid2()
123 {
124  unit_const_ptr u1 = ctx.loc1.get_unit(), u2 = ctx.loc2.get_unit();
125  if(u1) {
126  id1 = u1->id();
127  uid1 = u1->underlying_id();
128  }
129  if(u2) {
130  id2 = u2->id();
131  uid2 = u2->underlying_id();
132  }
133 }
134 
135 undo_event::undo_event(const config& first, const config& second, const config& weapons, const config& cmds)
136  : commands(cmds)
137  , data(weapons)
138  , loc1(first["x"], first["y"], wml_loc())
139  , loc2(second["x"], second["y"], wml_loc())
140  , filter_loc1(first["filter_x"], first["filter_y"], wml_loc())
141  , filter_loc2(second["filter_x"], second["filter_y"], wml_loc())
142  , uid1(first["underlying_id"].to_size_t())
143  , uid2(second["underlying_id"].to_size_t())
144  , id1(first["id"])
145  , id2(second["id"])
146 {
147 }
148 
150  : undo_event(cfg.child_or_empty("filter"),
151  cfg.child_or_empty("filter_second"),
152  cfg.child_or_empty("data"),
153  cfg.child_or_empty("command"))
154 {
155 }
156 
157 
158 namespace
159 {
160 unit_ptr get_unit(std::size_t uid, const std::string& id)
161 {
162  assert(resources::gameboard);
163  auto iter = resources::gameboard->units().find(uid);
164  if(!iter.valid() || iter->id() != id) {
165  return nullptr;
166  }
167  return iter.get_shared_ptr();
168 }
169 } // namespace
170 
172 {
173  undo_event& e = *this;
174  std::string tag = "undo";
175  assert(resources::lua_kernel);
176  assert(resources::gamedata);
177 
182  std::swap(x1, resources::gamedata->get_variable("x1"));
183  std::swap(y1, resources::gamedata->get_variable("y1"));
184  std::swap(x2, resources::gamedata->get_variable("x2"));
185  std::swap(y2, resources::gamedata->get_variable("y2"));
186 
187  std::unique_ptr<scoped_xy_unit> u1, u2;
188  if(unit_ptr who = get_unit(e.uid1, e.id1)) {
189  u1.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
190  }
191  if(unit_ptr who = get_unit(e.uid2, e.id2)) {
192  u2.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
193  }
194 
195  scoped_weapon_info w1("weapon", e.data.optional_child("first"));
196  scoped_weapon_info w2("second_weapon", e.data.optional_child("second"));
197 
198  game_events::queued_event q(tag, "", map_location(x1, y1, wml_loc()), map_location(x2, y2, wml_loc()), e.data);
199  if(e.lua_idx.has_value()) {
200  resources::lua_kernel->run_wml_event(*e.lua_idx, vconfig(e.commands), q);
201  } else {
202  resources::lua_kernel->run_wml_action("command", vconfig(e.commands), q);
203  }
205 
206  std::swap(x1, resources::gamedata->get_variable("x1"));
207  std::swap(y1, resources::gamedata->get_variable("y1"));
208  std::swap(x2, resources::gamedata->get_variable("x2"));
209  std::swap(y2, resources::gamedata->get_variable("y2"));
210  return true;
211 }
212 
213 void undo_event::write(config& cfg) const
214 {
215  undo_action::write(cfg);
216  auto& evt = *this;
217  if(evt.lua_idx.has_value()) {
218  // TODO: Log warning that this cannot be serialized
219  return;
220  }
221  config& entry = cfg;
222  config& first = entry.add_child("filter");
223  config& second = entry.add_child("filter_second");
224  entry.add_child("data", evt.data);
225  entry.add_child("command", evt.commands);
226  // First location
227  first["filter_x"] = evt.filter_loc1.wml_x();
228  first["filter_y"] = evt.filter_loc1.wml_y();
229  first["underlying_id"] = evt.uid1;
230  first["id"] = evt.id1;
231  first["x"] = evt.loc1.wml_x();
232  first["y"] = evt.loc1.wml_y();
233  // Second location
234  second["filter_x"] = evt.filter_loc2.wml_x();
235  second["filter_y"] = evt.filter_loc2.wml_y();
236  second["underlying_id"] = evt.uid2;
237  second["id"] = evt.id2;
238  second["x"] = evt.loc2.wml_x();
239  second["y"] = evt.loc2.wml_y();
240 }
241 
242 
244 
245 } // 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:66
std::map< std::string, t_factory > t_factory_map
Definition: undo_action.hpp:58
void add(t_step_ptr &&action)
Definition: undo_action.cpp:60
static t_factory_map & get_factories()
Definition: undo_action.cpp:83
undo_event(int fcn_idx, const config &args, const game_events::queued_event &ctx)
Definition: undo_action.cpp:94
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:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
config & add_child(config_key_type key)
Definition: config.cpp:436
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)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1339
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 formatting tag.
Definition: markup.hpp:50
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:843
constexpr auto reverse
Definition: ranges.hpp:40
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
std::string_view data
Definition: picture.cpp:178
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
virtual void write(config &cfg) const
Writes this into the provided config.
Definition: undo_action.hpp:91
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:45
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:33
#define e