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