The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
depcheck.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 - 2017 by Boldizs√°r Lipka <lipkab@zoho.com>
3  Part of the Battle for Wesnoth Project http://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 
16 
17 #include <algorithm>
18 
19 #include "formula/string_utils.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 #include "utils/general.hpp"
23 
26 #include "gui/dialogs/message.hpp"
27 
28 static lg::log_domain log_mp_create_depcheck("mp/create/depcheck");
29 #define DBG_MP LOG_STREAM(debug, log_mp_create_depcheck)
30 
31 namespace
32 {
33 // helper function
34 void copy_keys(config& out, const config& in, const std::string& type, bool copy_force_key = false)
35 {
36  if(in.has_attribute("allow_" + type)) {
37  out["allow_" + type] = in["allow_" + type];
38  } else if(in.has_attribute("disallow_" + type)) {
39  out["disallow_" + type] = in["disallow_" + type];
40  }
41 
42  if(in.has_attribute("ignore_incompatible_" + type)) {
43  out["ignore_incompatible_" + type] = in["ignore_incompatible_" + type];
44  }
45 
46  if(copy_force_key) {
47  if(in.has_attribute("force_" + type)) {
48  out["force_" + type] = in["force_" + type];
49  }
50  }
51 }
52 } // anonymous namespace
53 
54 namespace ng
55 {
56 namespace depcheck
57 {
58 manager::manager(const config& gamecfg, bool mp)
59  : depinfo_()
60  , era_()
61  , scenario_()
62  , mods_()
63  , prev_era_()
64  , prev_scenario_()
65  , prev_mods_()
66 {
67  DBG_MP << "Initializing the dependency manager" << std::endl;
68 
69  for(const config& cfg : gamecfg.child_range("modification")) {
70  component_availability type = cfg["type"].to_enum<component_availability>(component_availability::HYBRID);
71 
72  if((type != component_availability::MP || mp) && (type != component_availability::SP || !mp)) {
73  config info;
74  info["id"] = cfg["id"];
75  info["name"] = cfg["name"];
76 
77  copy_keys(info, cfg, "scenario");
78  copy_keys(info, cfg, "era");
79  copy_keys(info, cfg, "modification");
80 
81  depinfo_.add_child("modification", std::move(info));
82  }
83  }
84 
85  for(const config& cfg : gamecfg.child_range("era")) {
86  component_availability type = cfg["type"].to_enum<component_availability>(component_availability::MP);
87 
88  if((type != component_availability::MP || mp) && (type != component_availability::SP || !mp)) {
89  config info;
90  info["id"] = cfg["id"];
91  info["name"] = cfg["name"];
92 
93  copy_keys(info, cfg, "scenario");
94  copy_keys(info, cfg, "modification", true);
95 
96  depinfo_.add_child("era", std::move(info));
97  }
98  }
99 
100  for(const config& cfg : gamecfg.child_range("multiplayer")) {
101  if(cfg["allow_new_game"].to_bool(true)) {
102  config info;
103  info["id"] = cfg["id"];
104  info["name"] = cfg["name"];
105 
106  copy_keys(info, cfg, "era");
107  copy_keys(info, cfg, "modification", true);
108 
109  depinfo_.add_child("scenario", std::move(info));
110  }
111  }
112 
113  for(const config& cfg : gamecfg.child_range("campaign")) {
114  config info;
115  info["id"] = cfg["id"];
116  info["name"] = cfg["name"];
117  info["allow_era_choice"] = cfg["allow_era_choice"];
118 
119  copy_keys(info, cfg, "era");
120  copy_keys(info, cfg, "modification", true);
121 
122  depinfo_.add_child("scenario", std::move(info));
123  }
124 }
125 
126 void manager::save_state()
127 {
128  DBG_MP << "Saving current state" << std::endl;
129  prev_era_ = era_;
130  prev_scenario_ = scenario_;
131  prev_mods_ = mods_;
132 }
133 
134 void manager::revert()
135 {
136  DBG_MP << "Restoring previous state" << std::endl;
137  era_ = prev_era_;
138  scenario_ = prev_scenario_;
139  mods_ = prev_mods_;
140 }
141 
142 bool manager::exists(const elem& e) const
143 {
144  for(const config& cfg : depinfo_.child_range(e.type)) {
145  if(cfg["id"] == e.id) {
146  return true;
147  }
148  }
149 
150  return false;
151 }
152 
153 std::string manager::find_name_for(const elem& e) const
154 {
155  const config& cfg = depinfo_.find_child(e.type, "id", e.id);
156  return cfg["name"];
157 }
158 
159 std::vector<std::string> manager::get_required_not_installed(const elem& e) const
160 {
161  std::vector<std::string> result;
162 
163  std::vector<std::string> items = get_required(e);
164 
165  for(const std::string& str : items) {
166  if(!exists(elem(str, "modification"))) {
167  result.push_back(str);
168  }
169  }
170 
171  return result;
172 }
173 
174 std::vector<std::string> manager::get_required(const elem& e) const
175 {
176  std::vector<std::string> result;
177 
178  if(e.type == "modification") {
179  return result;
180  }
181 
182  config data = depinfo_.find_child(e.type, "id", e.id);
183 
184  if(data.has_attribute("force_modification")) {
185  result = utils::split(data["force_modification"], ',');
186  }
187 
188  return result;
189 }
190 
191 std::vector<std::string> manager::get_required_not_enabled(const elem& e) const
192 {
193  std::vector<std::string> required = get_required(e);
194  std::vector<std::string> result;
195 
196  for(std::string str : required) {
197  if(!utils::contains(mods_, str)) {
198  result.push_back(str);
199  }
200  }
201 
202  return result;
203 }
204 
205 std::vector<std::string> manager::get_conflicting_enabled(const elem& e) const
206 {
207  std::vector<std::string> result;
208 
209  for(const std::string& mod : mods_) {
210  if(conflicts(elem(mod, "modification"), e)) {
211  result.push_back(mod);
212  }
213  }
214 
215  return result;
216 }
217 
218 bool manager::conflicts(const elem& elem1, const elem& elem2, bool directonly) const
219 {
220  if(elem1 == elem2) {
221  return false;
222  }
223 
224  // We ignore nonexistant elements at this point, they will generate
225  // errors in change_era()/change_scenario() anyways.
226  if(!exists(elem1) || !exists(elem2)) {
227  return false;
228  }
229 
230  config data1 = depinfo_.find_child(elem1.type, "id", elem1.id);
231  config data2 = depinfo_.find_child(elem2.type, "id", elem2.id);
232 
233  // Whether we should skip the check entirely
234  if(data1.has_attribute("ignore_incompatible_" + elem2.type)) {
235  std::vector<std::string> ignored = utils::split(data1["ignore_incompatible_" + elem2.type]);
236 
237  if(utils::contains(ignored, elem2.id)) {
238  return false;
239  }
240  }
241 
242  if(data2.has_attribute("ignore_incompatible_" + elem1.type)) {
243  std::vector<std::string> ignored = utils::split(data2["ignore_incompatible_" + elem1.type]);
244 
245  if(utils::contains(ignored, elem1.id)) {
246  return false;
247  }
248  }
249 
250  if((elem1.type == "era" && data2["allow_era_choice"].to_bool(false)) ||(elem2.type == "era" && data1["allow_era_choice"].to_bool(false))) {
251  return false;
252  }
253 
254  bool result = false;
255 
256  // Checking for direct conflicts between elem1 and elem2
257  if(data1.has_attribute("allow_" + elem2.type)) {
258  std::vector<std::string> allowed = utils::split(data1["allow_" + elem2.type]);
259 
260  result = !utils::contains(allowed, elem2.id) && !requires(elem1, elem2);
261  } else if(data1.has_attribute("disallow_" + elem2.type)) {
262  std::vector<std::string> disallowed = utils::split(data1["disallow_" + elem2.type]);
263 
264  result = utils::contains(disallowed, elem2.id);
265  }
266 
267  if(data2.has_attribute("allow_" + elem1.type)) {
268  std::vector<std::string> allowed = utils::split(data2["allow_" + elem1.type]);
269 
270  result = result || (!utils::contains(allowed, elem1.id) && !requires(elem2, elem1));
271  } else if(data2.has_attribute("disallow_" + elem1.type)) {
272  std::vector<std::string> disallowed = utils::split(data2["disallow_" + elem1.type]);
273 
274  result = result || utils::contains(disallowed, elem1.id);
275  }
276 
277  if(result) {
278  return true;
279  }
280 
281  // Checking for indirect conflicts (i.e. conflicts between dependencies)
282  if(!directonly) {
283  std::vector<std::string> req1 = get_required(elem1), req2 = get_required(elem2);
284 
285  for(const std::string& s : req1) {
286  elem m(s, "modification");
287 
288  if(conflicts(elem2, m, true)) {
289  return true;
290  }
291  }
292 
293  for(const std::string& s : req2) {
294  elem m(s, "modification");
295 
296  if(conflicts(elem1, m, true)) {
297  return true;
298  }
299  }
300 
301  for(const std::string& id1 : req1) {
302  elem m1(id1, "modification");
303 
304  for(const std::string& id2 : req2) {
305  elem m2(id2, "modification");
306 
307  if(conflicts(m1, m2)) {
308  return true;
309  }
310  }
311  }
312  }
313 
314  return false;
315 }
316 
317 bool manager::requires(const elem& elem1, const elem& elem2) const
318 {
319  if(elem2.type != "modification") {
320  return false;
321  }
322 
323  config data = depinfo_.find_child(elem1.type, "id", elem1.id);
324 
325  if(data.has_attribute("force_modification")) {
326  std::vector<std::string> required = utils::split(data["force_modification"]);
327 
328  return utils::contains(required, elem2.id);
329  }
330 
331  return false;
332 }
333 
334 void manager::try_era(const std::string& id, bool force)
335 {
336  save_state();
337 
338  if(force) {
339  era_ = id;
340  } else if(!change_era(id)) {
341  revert();
342  }
343 }
344 
345 void manager::try_scenario(const std::string& id, bool force)
346 {
347  save_state();
348 
349  if(force) {
350  scenario_ = id;
351  } else if(!change_scenario(id)) {
352  revert();
353  }
354 }
355 
356 void manager::try_modifications(const std::vector<std::string>& ids, bool force)
357 {
358  save_state();
359 
360  if(force) {
361  mods_ = ids;
362  } else if(!change_modifications(ids)) {
363  revert();
364  }
365 }
366 
367 void manager::try_modification_by_index(int index, bool activate, bool force)
368 {
369  std::string id = depinfo_.child("modification", index)["id"];
370  std::vector<std::string> mods_copy = mods_;
371 
372  if(activate) {
373  if(std::find(mods_copy.begin(), mods_copy.end(), id) == mods_copy.end()) {
374  mods_copy.push_back(id);
375  }
376  } else {
377  std::vector<std::string>::iterator pos = std::find(mods_copy.begin(), mods_copy.end(), id);
378  if(pos != mods_copy.end()) {
379  mods_copy.erase(pos);
380  }
381  }
382 
383  try_modifications(mods_copy, force);
384 }
385 
386 void manager::try_era_by_index(int index, bool force)
387 {
388  try_era(depinfo_.child("era", index)["id"], force);
389 }
390 
391 void manager::try_scenario_by_index(int index, bool force)
392 {
393  try_scenario(depinfo_.child("scenario", index)["id"], force);
394 }
395 
396 int manager::get_era_index() const
397 {
398  int result = 0;
399  for(const config& i : depinfo_.child_range("era")) {
400  if(i["id"] == era_) {
401  return result;
402  }
403 
404  result++;
405  }
406 
407  return -1;
408 }
409 
410 int manager::get_scenario_index() const
411 {
412  int result = 0;
413 
414  for(const config& i : depinfo_.child_range("scenario")) {
415  if(i["id"] == scenario_) {
416  return result;
417  }
418 
419  result++;
420  }
421 
422  return -1;
423 }
424 
425 bool manager::is_modification_active(int index) const
426 {
427  std::string id = depinfo_.child("modification", index)["id"];
428  return std::find(mods_.begin(), mods_.end(), id) != mods_.end();
429 }
430 
431 bool manager::is_modification_active(const std::string id) const
432 {
433  return std::find(mods_.begin(), mods_.end(), id) != mods_.end();
434 }
435 
436 bool manager::enable_mods_dialog(const std::vector<std::string>& mods, const std::string& requester)
437 {
438  std::vector<std::string> items;
439  for(const std::string& mod : mods) {
440  items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
441  }
442 
443  gui2::dialogs::depcheck_confirm_change dialog(true, items, requester);
444  return dialog.show();
445 }
446 
447 bool manager::disable_mods_dialog(const std::vector<std::string>& mods, const std::string& requester)
448 {
449  std::vector<std::string> items;
450  for(const std::string& mod : mods) {
451  items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
452  }
453 
454  gui2::dialogs::depcheck_confirm_change dialog(false, items, requester);
455  return dialog.show();
456 }
457 
458 std::string manager::change_era_dialog(const std::vector<std::string>& eras)
459 {
460  std::vector<std::string> items;
461  for(const std::string& era : eras) {
462  items.push_back(depinfo_.find_child("era", "id", era)["name"]);
463  }
464 
466 
467  if(dialog.show()) {
468  return eras[dialog.result()];
469  }
470 
471  return "";
472 }
473 
474 std::string manager::change_scenario_dialog(const std::vector<std::string>& scenarios)
475 {
476  std::vector<std::string> items;
477  for(const std::string& scenario : scenarios) {
478  items.push_back(depinfo_.find_child("scenario", "id", scenario)["name"]);
479  }
480 
482  if(dialog.show()) {
483  return scenarios[dialog.result()];
484  }
485 
486  return "";
487 }
488 
489 void manager::failure_dialog(const std::string& msg)
490 {
491  gui2::show_message(_("Failed to resolve dependencies"), msg, _("OK"));
492 }
493 
494 void manager::insert_element(component_type type, const config& data, int index)
495 {
496  std::string type_str;
497 
498  switch(type) {
499  case ERA:
500  type_str = "era";
501  break;
502  case SCENARIO:
503  type_str = "scenario";
504  break;
505  case MODIFICATION:
506  type_str = "modification";
507  }
508 
509  depinfo_.add_child_at(type_str, data, index);
510 }
511 
512 bool manager::change_scenario(const std::string& id)
513 {
514  // Checking for missing dependencies
515  if(!get_required_not_installed(elem(id, "scenario")).empty()) {
516  std::string msg = _("Scenario can't be activated. Some dependencies are missing: ");
517 
518  msg += utils::join(get_required_not_installed(elem(id, "scenario")), ", ");
519 
520  failure_dialog(msg);
521  return false;
522  }
523 
524  scenario_ = id;
525 
526  elem scen = elem(id, "scenario");
527  std::string scen_name = find_name_for(scen);
528 
529  // Firstly, we check if we have to enable/disable any mods
530  std::vector<std::string> req = get_required_not_enabled(scen);
531  std::vector<std::string> con = get_conflicting_enabled(scen);
532 
533  if(!req.empty()) {
534  if(!enable_mods_dialog(req, scen_name)) {
535  return false;
536  }
537  }
538 
539  if(!con.empty()) {
540  if(!disable_mods_dialog(con, scen_name)) {
541  return false;
542  }
543  }
544 
545  std::vector<std::string> newmods = req;
546  for(const std::string& i : mods_) {
547  if(!utils::contains(con, i)) {
548  newmods.push_back(i);
549  }
550  }
551 
552  mods_ = newmods;
553 
554  // Now checking if the currently selected era conflicts the scenario
555  // and changing era if necessary
556  if(!conflicts(scen, elem(era_, "era"))) {
557  return true;
558  }
559 
560  std::vector<std::string> compatible;
561  for(const config& i : depinfo_.child_range("era")) {
562  if(!conflicts(scen, elem(i["id"], "era"))) {
563  compatible.push_back(i["id"]);
564  }
565  }
566 
567  if(!compatible.empty()) {
568  era_ = change_era_dialog(compatible);
569  } else {
570  failure_dialog(_("No compatible eras found."));
571  return false;
572  }
573 
574  if(era_.empty()) {
575  return false;
576  }
577 
578  return change_era(era_);
579 }
580 
581 bool manager::change_era(const std::string& id)
582 {
583  // Checking for missing dependencies
584  if(!get_required_not_installed(elem(id, "era")).empty()) {
585  std::string msg = _("Era can't be activated. Some dependencies are missing: ");
586 
587  msg += utils::join(get_required_not_installed(elem(id, "era")), ", ");
588  failure_dialog(msg);
589  return false;
590  }
591 
592  era_ = id;
593 
594  elem era = elem(id, "era");
595  std::string era_name = find_name_for(era);
596 
597  std::vector<std::string> req = get_required_not_enabled(era);
598  std::vector<std::string> con = get_conflicting_enabled(era);
599 
600  // Firstly, we check if we have to enable/disable any mods
601  if(!req.empty()) {
602  if(!enable_mods_dialog(req, era_name)) {
603  return false;
604  }
605  }
606 
607  if(!con.empty()) {
608  if(!disable_mods_dialog(con, era_name)) {
609  return false;
610  }
611  }
612 
613  std::vector<std::string> newmods = req;
614  for(const std::string& i : mods_) {
615  if(!utils::contains(con, i)) {
616  newmods.push_back(i);
617  }
618  }
619 
620  mods_ = newmods;
621 
622  // Now checking if the currently selected scenario conflicts the era
623  // and changing scenario if necessary
624  if(!conflicts(era, elem(scenario_, "scenario"))) {
625  return true;
626  }
627 
628  std::vector<std::string> compatible;
629  for(const config& i : depinfo_.child_range("scenario")) {
630  if(!conflicts(era, elem(i["id"], "scenario"))) {
631  compatible.push_back(i["id"]);
632  }
633  }
634 
635  if(!compatible.empty()) {
636  scenario_ = change_scenario_dialog(compatible);
637  } else {
638  failure_dialog(_("No compatible scenarios found."));
639  return false;
640  }
641 
642  if(scenario_.empty()) {
643  return false;
644  }
645 
646  return change_scenario(scenario_);
647 }
648 
649 bool manager::change_modifications(const std::vector<std::string>& modifications)
650 {
651  // Checking if the selected combination of mods is valid at all
652  std::vector<std::string> filtered;
653  for(const std::string& i : modifications) {
654  bool ok = true;
655  elem ei(i, "modification");
656 
657  for(const std::string& j : filtered) {
658  ok = ok && !conflicts(ei, elem(j, "modification"));
659  }
660 
661  if(ok) {
662  filtered.push_back(i);
663  }
664  }
665 
666  if(filtered.size() != modifications.size()) {
667  failure_dialog(_("Not all of the chosen modifications are compatible."
668  " Some of them will be disabled."));
669  }
670 
671  mods_ = filtered;
672 
673  // Checking if the currently selected era is compatible with the set
674  // modifications, and changing era if necessary
675  std::vector<std::string> compatible;
676  for(const config& c : depinfo_.child_range("era")) {
677  elem era(c["id"], "era");
678  bool ok = true;
679 
680  for(const std::string& s : mods_) {
681  ok = ok && !conflicts(era, elem(s, "modification"));
682  }
683 
684  if(ok) {
685  compatible.push_back(era.id);
686  }
687  }
688 
689  if(!utils::contains(compatible, era_)) {
690  if(!compatible.empty()) {
691  era_ = change_era_dialog(compatible);
692  } else {
693  failure_dialog(_("No compatible eras found."));
694  return false;
695  }
696 
697  if(era_.empty()) {
698  return false;
699  }
700 
701  if(!change_era(era_)) {
702  return false;
703  }
704  } else {
705  if(!change_era(era_)) {
706  return false;
707  }
708  }
709 
710  compatible.clear();
711 
712  // Checking if the currently selected scenario is compatible with
713  // the set modifications, and changing scenario if necessary
714  for(const config& c : depinfo_.child_range("scenario")) {
715  elem scen(c["id"], "scenario");
716  bool ok = true;
717  for(const std::string& s : mods_) {
718  ok = ok && !conflicts(scen, elem(s, "modification"));
719  }
720 
721  if(ok) {
722  compatible.push_back(scen.id);
723  }
724  }
725 
726  if(!utils::contains(compatible, scenario_)) {
727  if(!compatible.empty()) {
728  scenario_ = change_scenario_dialog(compatible);
729  } else {
730  failure_dialog(_("No compatible scenarios found."));
731  return false;
732  }
733 
734  if(scenario_.empty()) {
735  return false;
736  }
737 
738  return change_scenario(scenario_);
739  } else {
740  if(!change_scenario(scenario_)) {
741  return false;
742  }
743  }
744 
745  return true;
746 }
747 
748 } // namespace depcheck
749 
750 } // namespace ng
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:152
std::vector< char_t > string
size_t index(const utf8::string &str, const size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
std::string era()
Definition: game.cpp:700
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:763
logger & info()
Definition: log.cpp:91
child_itors child_range(config_key_type key)
Definition: config.cpp:343
#define DBG_MP
Definition: depcheck.cpp:29
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
const std::vector< std::string > items
int result() const
Returns the selected item.
bool show(const unsigned auto_close_time=0)
Shows the window.
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:70
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:48
unsigned in
If equal to search_counter, the node is off the list.
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: image.cpp:1135
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
static lg::log_domain log_mp_create_depcheck("mp/create/depcheck")
bool has_attribute(config_key_type key) const
Definition: config.cpp:196
static map_location::DIRECTION s
size_t i
Definition: function.cpp:933
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:730
bool find(E event, F functor)
Tests whether an event handler is available.
Standard logging facilities (interface).
#define e
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
mock_char c
std::string::const_iterator iterator
Definition: tokenizer.hpp:24