1 /*
2    Copyright (C) 2017-2018 the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY.
10 
11    See the COPYING file for more details.
12 */
13 
14 #include "actions/undo_action.hpp"
15 #include "game_board.hpp"
16 #include "scripting/game_lua_kernel.hpp"
17 #include "resources.hpp"
18 #include "variable.hpp" // vconfig
19 #include "game_data.hpp"
20 #include "units/unit.hpp"
21 #include "sound.hpp"
22 
23 #include <cassert>
24 #include <iterator>
25 #include <algorithm>
26 
27 namespace actions
28 {
29 
undo_event(const config & cmds,const game_events::queued_event & ctx)30 undo_event::undo_event(const config& cmds, const game_events::queued_event& ctx)
31 	: commands(cmds)
32 	, data(ctx.data)
33 	, loc1(ctx.loc1)
34 	, loc2(ctx.loc2)
35 	, filter_loc1(ctx.loc1.filter_loc())
36 	, filter_loc2(ctx.loc2.filter_loc())
37 	, uid1(), uid2()
38 {
39 	unit_const_ptr u1 = ctx.loc1.get_unit(), u2 = ctx.loc2.get_unit();
40 	if(u1) {
41 		id1 = u1->id();
42 		uid1 = u1->underlying_id();
43 	}
44 	if(u2) {
45 		id2 = u2->id();
46 		uid2 = u2->underlying_id();
47 	}
48 }
49 
undo_event(const config & first,const config & second,const config & weapons,const config & cmds)50 undo_event::undo_event(const config& first, const config& second, const config& weapons, const config& cmds)
51 	: commands(cmds)
52 	, data(weapons)
53 	, loc1(first["x"], first["y"], wml_loc())
54 	, loc2(second["x"], second["y"], wml_loc())
55 	, filter_loc1(first["filter_x"], first["filter_y"], wml_loc())
56 	, filter_loc2(second["filter_x"], second["filter_y"], wml_loc())
57 	, uid1(first["underlying_id"])
58 	, uid2(second["underlying_id"])
59 	, id1(first["id"])
60 	, id2(second["id"])
61 {
62 }
63 
undo_action()64 undo_action::undo_action()
65 	: undo_action_base()
66 	, unit_id_diff(synced_context::get_unit_id_diff())
67 {
68 	auto& undo = synced_context::get_undo_commands();
69 	auto command_transformer = [](const std::pair<config, game_events::queued_event>& p) {
70 		return undo_event(p.first, p.second);
71 	};
72 	std::transform(undo.begin(), undo.end(), std::back_inserter(umc_commands_undo), command_transformer);
73 	undo.clear();
74 }
75 
undo_action(const config & cfg)76 undo_action::undo_action(const config& cfg)
77 	: undo_action_base()
78 	, unit_id_diff(cfg["unit_id_diff"])
79 {
80 	read_event_vector(umc_commands_undo, cfg, "undo_actions");
81 }
82 
83 namespace {
get_unit(size_t uid,const std::string & id)84 	unit_ptr get_unit(size_t uid, const std::string& id) {
85 		assert(resources::gameboard);
86 		auto iter = resources::gameboard->units().find(uid);
87 		if(!iter.valid() || iter->id() != id) {
88 			return nullptr;
89 		}
90 		return iter.get_shared_ptr();
91 	}
execute_event(const undo_event & e,std::string tag)92 	void execute_event(const undo_event& e, std::string tag) {
93 		assert(resources::lua_kernel);
94 		assert(resources::gamedata);
95 
96 		config::attribute_value& x1 = resources::gamedata->get_variable("x1");
97 		config::attribute_value& y1 = resources::gamedata->get_variable("y1");
98 		config::attribute_value& x2 = resources::gamedata->get_variable("x2");
99 		config::attribute_value& y2 = resources::gamedata->get_variable("y2");
100 		int oldx1 = x1, oldy1 = y1, oldx2 = x2, oldy2 = y2;
101 		x1 = e.filter_loc1.wml_x(); y1 = e.filter_loc1.wml_y();
102 		x2 = e.filter_loc2.wml_x(); y2 = e.filter_loc2.wml_y();
103 
104 		std::unique_ptr<scoped_xy_unit> u1, u2;
105 		if(unit_ptr who = get_unit(e.uid1, e.id1)) {
106 			u1.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
107 		}
108 		if(unit_ptr who = get_unit(e.uid2, e.id2)) {
109 			u2.reset(new scoped_xy_unit("unit", who->get_location(), resources::gameboard->units()));
110 		}
111 
112 		scoped_weapon_info w1("weapon", e.data.child("first"));
113 		scoped_weapon_info w2("second_weapon", e.data.child("second"));
114 
115 		game_events::queued_event q(tag, "", map_location(x1, y1, wml_loc()), map_location(x2, y2, wml_loc()), e.data);
116 		resources::lua_kernel->run_wml_action("command", vconfig(e.commands), q);
117 		sound::commit_music_changes();
118 
119 		x1 = oldx1; y1 = oldy1;
120 		x2 = oldx2; y2 = oldy2;
121 	}
122 }
123 
execute_undo_umc_wml()124 void undo_action::execute_undo_umc_wml()
125 {
126 	for(const undo_event& e : umc_commands_undo)
127 	{
128 		execute_event(e, "undo");
129 	}
130 }
131 
132 
write(config & cfg) const133 void undo_action::write(config & cfg) const
134 {
135 	cfg["unit_id_diff"] = unit_id_diff;
136 	write_event_vector(umc_commands_undo, cfg, "undo_actions");
137 	undo_action_base::write(cfg);
138 }
139 
read_event_vector(event_vector & vec,const config & cfg,const std::string & tag)140 void undo_action::read_event_vector(event_vector& vec, const config& cfg, const std::string& tag)
141 {
142 	for(auto c : cfg.child_range(tag)) {
143 		vec.emplace_back(c.child("filter"), c.child("filter_second"), c.child("filter_weapons"), c.child("commands"));
144 	}
145 }
146 
write_event_vector(const event_vector & vec,config & cfg,const std::string & tag)147 void undo_action::write_event_vector(const event_vector& vec, config& cfg, const std::string& tag)
148 {
149 	for(const auto& evt : vec)
150 	{
151 		config& entry = cfg.add_child(tag);
152 		config& first = entry.add_child("filter");
153 		config& second = entry.add_child("filter_second");
154 		entry.add_child("filter_weapons", evt.data);
155 		entry.add_child("command", evt.commands);
156 		// First location
157 		first["filter_x"] = evt.filter_loc1.wml_x();
158 		first["filter_y"] = evt.filter_loc1.wml_y();
159 		first["underlying_id"] = evt.uid1;
160 		first["id"] = evt.id1;
161 		first["x"] = evt.loc1.wml_x();
162 		first["y"] = evt.loc1.wml_y();
163 		// Second location
164 		second["filter_x"] = evt.filter_loc2.wml_x();
165 		second["filter_y"] = evt.filter_loc2.wml_y();
166 		second["underlying_id"] = evt.uid2;
167 		second["id"] = evt.id2;
168 		second["x"] = evt.loc2.wml_x();
169 		second["y"] = evt.loc2.wml_y();
170 	}
171 }
172 
173 }
174