1 /*
2    Copyright (C) 2010 - 2018 by Jody Northup
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #include "game_data.hpp"
16 #include "gettext.hpp"
17 #include "log.hpp"
18 #include "persist_context.hpp"
19 #include "persist_manager.hpp"
20 #include "persist_var.hpp"
21 #include "play_controller.hpp"
22 #include "synced_user_choice.hpp"
23 #include "resources.hpp"
24 #include "variable.hpp"
25 
26 #include <cassert>
27 
28 //TODO: remove LOG_PERSIST, ERR_PERSIST from persist_context.hpp to .cpp files.
29 #define DBG_PERSIST LOG_STREAM(debug, log_persist)
30 #define ERR_PERSIST LOG_STREAM(err, log_persist)
31 
32 struct persist_choice: mp_sync::user_choice {
33 	const persist_context &ctx;
34 	std::string var_name;
35 	int side;
persist_choicepersist_choice36 	persist_choice(const persist_context &context,const std::string &name, int side_num)
37 		: ctx(context)
38 		, var_name(name)
39 		, side(side_num) {
40 	}
query_userpersist_choice41 	virtual config query_user(int /*side_for*/) const {
42 		//side can be different from side_for: if side was null-controlled
43 		//then get_user_choice will use the next non-null-controlled side instead
44 		config ret;
45 		ret["side"] = side;
46 		ret.add_child("variables",ctx.get_var(var_name));
47 		return ret;
48 	}
random_choicepersist_choice49 	virtual config random_choice(int /*side_for*/) const {
50 		return config();
51 	}
52 
descriptionpersist_choice53 	virtual std::string description() const
54 	{
55 		// TRANSLATORS:  In networked games, this text is shown for other
56 		// clients, while they wait to receive the content of a global variable
57 		// from another player. This text will be embedded into a sentence.
58 		return _("waiting for^a global variable");
59 	}
is_visiblepersist_choice60 	virtual bool is_visible() const { return false; }
61 };
62 
get_global_variable(persist_context & ctx,const vconfig & pcfg)63 static void get_global_variable(persist_context &ctx, const vconfig &pcfg)
64 {
65 	std::string global = pcfg["from_global"];
66 	std::string local = pcfg["to_local"];
67 	config::attribute_value pcfg_side = pcfg["side"];
68 	const int side = pcfg_side.to_int(resources::controller->current_side());
69 	persist_choice choice(ctx, global, side);
70 	config cfg = mp_sync::get_user_choice("global_variable",choice,side).child("variables");
71 	try
72 	{
73 		if (cfg) {
74 			size_t arrsize = cfg.child_count(global);
75 			if (arrsize == 0) {
76 				resources::gamedata->set_variable(local,cfg[global]);
77 			} else {
78 				resources::gamedata->clear_variable(local);
79 				for (size_t i = 0; i < arrsize; i++)
80 					resources::gamedata->add_variable_cfg(local,cfg.child(global,i));
81 			}
82 		} else {
83 			resources::gamedata->set_variable(local,"");
84 		}
85 	}
86 	catch(const invalid_variablename_exception&)
87 	{
88 		ERR_PERSIST << "cannot store global variable into invalid variablename " << local << std::endl;
89 	}
90 }
91 
clear_global_variable(persist_context & ctx,const vconfig & pcfg)92 static void clear_global_variable(persist_context &ctx, const vconfig &pcfg)
93 {
94 	std::string global = pcfg["global"];
95 	ctx.clear_var(global, pcfg["immediate"].to_bool());
96 }
97 
set_global_variable(persist_context & ctx,const vconfig & pcfg)98 static void set_global_variable(persist_context &ctx, const vconfig &pcfg)
99 {
100 	if (pcfg["from_local"].empty()) {
101 		clear_global_variable(ctx, pcfg);
102 	} else {
103 		std::string global = pcfg["to_global"];
104 		std::string local = pcfg["from_local"];
105 		config val;
106 		const config &vars = resources::gamedata->get_variables();
107 		size_t arraylen = vars.child_count(local);
108 		if (arraylen == 0) {
109 			try
110 			{
111 				val = pack_scalar(global,resources::gamedata->get_variable(local));
112 			}
113 			catch(const invalid_variablename_exception&)
114 			{
115 				val.clear();
116 			}
117 		} else {
118 			for (size_t i = 0; i < arraylen; i++)
119 				val.add_child(global,vars.child(local,i));
120 		}
121 		ctx.set_var(global, val, pcfg["immediate"].to_bool());
122 	}
123 }
verify_and_get_global_variable(const vconfig & pcfg)124 void verify_and_get_global_variable(const vconfig &pcfg)
125 {
126 	bool valid = true;
127 	if (!pcfg.has_attribute("from_global")) {
128 		ERR_PERSIST << "[get_global_variable] missing required attribute \"from_global\"";
129 		valid = false;
130 	}
131 	if (!pcfg.has_attribute("to_local")) {
132 		ERR_PERSIST << "[get_global_variable] missing required attribute \"to_local\"";
133 		valid = false;
134 	}
135 	// TODO: allow for global namespace.
136 	if (!pcfg.has_attribute("namespace")) {
137 		ERR_PERSIST << "[get_global_variable] missing attribute \"namespace\"";
138 		valid = false;
139 	}
140 	if (resources::controller->is_networked_mp()) {
141 			DBG_PERSIST << "verify_and_get_global_variable with from_global=" << pcfg["from_global"] << " from side " << pcfg["side"] << "\n";
142 			config::attribute_value pcfg_side = pcfg["side"];
143 			int side = (pcfg_side.str() == "global" || pcfg_side.empty()) ? resources::controller->current_side() : pcfg_side.to_int();
144 			if (!resources::gameboard->has_team(side)) {
145 				ERR_PERSIST << "[get_global_variable] attribute \"side\" specifies invalid side number." << "\n";
146 				valid = false;
147 			}
148 			DBG_PERSIST <<  "end verify_and_get_global_variable with from_global=" << pcfg["from_global"] << " from side " << pcfg["side"] << "\n";
149 	}
150 	if (valid)
151 	{
152 		persist_context &ctx = resources::persist->get_context((pcfg["namespace"]));
153 		if (ctx.valid()) {
154 			get_global_variable(ctx,pcfg);
155 		} else {
156 			LOG_PERSIST << "Error: [get_global_variable] attribute \"namespace\" is not valid.";
157 		}
158 	}
159 }
verify_and_set_global_variable(const vconfig & pcfg)160 void verify_and_set_global_variable(const vconfig &pcfg)
161 {
162 	bool valid = true;
163 	if (!pcfg.has_attribute("to_global")) {
164 		ERR_PERSIST << "[set_global_variable] missing required attribute \"to_global\"";
165 		valid = false;
166 	}
167 	if (!pcfg.has_attribute("from_local")) {
168 		LOG_PERSIST << "Warning: [set_global_variable] missing attribute \"from_local\", global variable will be cleared";
169 	}
170 	// TODO: allow for global namespace.
171 	if (!pcfg.has_attribute("namespace")) {
172 		ERR_PERSIST << "[set_global_variable] missing attribute \"namespace\" and no global namespace provided.";
173 		valid = false;
174 	}
175 	if (resources::controller->is_networked_mp()) {
176 		config::attribute_value pcfg_side = pcfg["side"];
177 		int side = pcfg_side;
178 		//Check side matching only if the side is not "global" or empty.
179 		if (pcfg_side.str() != "global" && !pcfg_side.empty()) {
180 			//Ensure that the side is valid.
181 			if (!resources::gameboard->has_team(side)) {
182 				ERR_PERSIST << "[set_global_variable] attribute \"side\" specifies invalid side number.";
183 				valid = false;
184 			} else if (resources::gameboard->get_team(side).is_empty()) {
185 				LOG_PERSIST << "[set_global_variable] attribute \"side\" specifies a null-controlled side number.";
186 				valid = false;
187 			} else {
188 				//Set the variable only if it is meant for a side we control
189 				valid = resources::gameboard->get_team(side).is_local();
190 			}
191 		}
192 	}
193 	if (valid)
194 	{
195 		persist_context &ctx = resources::persist->get_context((pcfg["namespace"]));
196 		if (ctx.valid()) {
197 			set_global_variable(ctx,pcfg);
198 		} else {
199 			LOG_PERSIST << "Error: [set_global_variable] attribute \"namespace\" is not valid.";
200 		}
201 	}
202 }
verify_and_clear_global_variable(const vconfig & pcfg)203 void verify_and_clear_global_variable(const vconfig &pcfg)
204 {
205 	bool valid = true;
206 	if (!pcfg.has_attribute("global")) {
207 		ERR_PERSIST << "[clear_global_variable] missing required attribute \"from_global\"";
208 		valid = false;
209 	}
210 	if (!pcfg.has_attribute("namespace")) {
211 		ERR_PERSIST << "[clear_global_variable] missing attribute \"namespace\" and no global namespace provided.";
212 		valid = false;
213 	}
214 	if (resources::controller->is_networked_mp()) {
215 		config::attribute_value pcfg_side = pcfg["side"];
216 		const int side = pcfg_side.to_int();
217 		//Check side matching only if the side is not "global" or empty.
218 		if (pcfg_side.str() != "global" && !pcfg_side.empty()) {
219 			//Ensure that the side is valid.
220 			if (!resources::gameboard->has_team(side)) {
221 				ERR_PERSIST << "[clear_global_variable] attribute \"side\" specifies invalid side number.";
222 				valid = false;
223 			} else if (resources::gameboard->get_team(side).is_empty()) {
224 				LOG_PERSIST << "[clear_global_variable] attribute \"side\" specifies a null-controlled side number.";
225 				valid = false;
226 			} else {
227 				//Clear the variable only if it is meant for a side we control
228 				valid = resources::gameboard->get_team(side).is_local();
229 			}
230 		}
231 	}
232 	if (valid)
233 	{
234 		persist_context &ctx = resources::persist->get_context((pcfg["namespace"]));
235 		if (ctx.valid()) {
236 			clear_global_variable(ctx,pcfg);
237 		} else {
238 			LOG_PERSIST << "Error: [clear_global_variable] attribute \"namespace\" is not valid.";
239 		}
240 	}
241 }
242