The Battle for Wesnoth  1.15.0-dev
depcheck.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 - 2018 by Boldizs√°r Lipka <lipkab@zoho.com>
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 
16 
17 #include <algorithm>
18 
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 nonexistent 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  return gui2::dialogs::depcheck_confirm_change::execute(true, items, requester);
444 }
445 
446 bool manager::disable_mods_dialog(const std::vector<std::string>& mods, const std::string& requester)
447 {
448  std::vector<std::string> items;
449  for(const std::string& mod : mods) {
450  items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
451  }
452 
453  return gui2::dialogs::depcheck_confirm_change::execute(false, items, requester);
454 }
455 
456 std::string manager::change_era_dialog(const std::vector<std::string>& eras)
457 {
458  std::vector<std::string> items;
459  for(const std::string& era : eras) {
460  items.push_back(depinfo_.find_child("era", "id", era)["name"]);
461  }
462 
464 
465  if(dialog.show()) {
466  return eras[dialog.result()];
467  }
468 
469  return "";
470 }
471 
472 std::string manager::change_scenario_dialog(const std::vector<std::string>& scenarios)
473 {
474  std::vector<std::string> items;
475  for(const std::string& scenario : scenarios) {
476  items.push_back(depinfo_.find_child("scenario", "id", scenario)["name"]);
477  }
478 
480  if(dialog.show()) {
481  return scenarios[dialog.result()];
482  }
483 
484  return "";
485 }
486 
487 void manager::failure_dialog(const std::string& msg)
488 {
489  gui2::show_message(_("Failed to resolve dependencies"), msg, _("OK"));
490 }
491 
492 void manager::insert_element(component_type type, const config& data, int index)
493 {
494  std::string type_str;
495 
496  switch(type) {
497  case ERA:
498  type_str = "era";
499  break;
500  case SCENARIO:
501  type_str = "scenario";
502  break;
503  case MODIFICATION:
504  type_str = "modification";
505  }
506 
507  depinfo_.add_child_at(type_str, data, index);
508 }
509 
510 bool manager::change_scenario(const std::string& id)
511 {
512  // Checking for missing dependencies
513  if(!get_required_not_installed(elem(id, "scenario")).empty()) {
514  std::string msg = _("Scenario can't be activated. Some dependencies are missing: ");
515 
516  msg += utils::join(get_required_not_installed(elem(id, "scenario")), ", ");
517 
518  failure_dialog(msg);
519  return false;
520  }
521 
522  scenario_ = id;
523 
524  elem scen = elem(id, "scenario");
525  std::string scen_name = find_name_for(scen);
526 
527  // Firstly, we check if we have to enable/disable any mods
528  std::vector<std::string> req = get_required_not_enabled(scen);
529  std::vector<std::string> con = get_conflicting_enabled(scen);
530 
531  if(!req.empty()) {
532  if(!enable_mods_dialog(req, scen_name)) {
533  return false;
534  }
535  }
536 
537  if(!con.empty()) {
538  if(!disable_mods_dialog(con, scen_name)) {
539  return false;
540  }
541  }
542 
543  std::vector<std::string> newmods = req;
544  for(const std::string& i : mods_) {
545  if(!utils::contains(con, i)) {
546  newmods.push_back(i);
547  }
548  }
549 
550  mods_ = newmods;
551 
552  // Now checking if the currently selected era conflicts the scenario
553  // and changing era if necessary
554  if(!conflicts(scen, elem(era_, "era"))) {
555  return true;
556  }
557 
558  std::vector<std::string> compatible;
559  for(const config& i : depinfo_.child_range("era")) {
560  if(!conflicts(scen, elem(i["id"], "era"))) {
561  compatible.push_back(i["id"]);
562  }
563  }
564 
565  if(!compatible.empty()) {
566  era_ = change_era_dialog(compatible);
567  } else {
568  failure_dialog(_("No compatible eras found."));
569  return false;
570  }
571 
572  if(era_.empty()) {
573  return false;
574  }
575 
576  return change_era(era_);
577 }
578 
579 bool manager::change_era(const std::string& id)
580 {
581  // Checking for missing dependencies
582  if(!get_required_not_installed(elem(id, "era")).empty()) {
583  std::string msg = _("Era can't be activated. Some dependencies are missing: ");
584 
585  msg += utils::join(get_required_not_installed(elem(id, "era")), ", ");
586  failure_dialog(msg);
587  return false;
588  }
589 
590  era_ = id;
591 
592  elem era = elem(id, "era");
593  std::string era_name = find_name_for(era);
594 
595  std::vector<std::string> req = get_required_not_enabled(era);
596  std::vector<std::string> con = get_conflicting_enabled(era);
597 
598  // Firstly, we check if we have to enable/disable any mods
599  if(!req.empty()) {
600  if(!enable_mods_dialog(req, era_name)) {
601  return false;
602  }
603  }
604 
605  if(!con.empty()) {
606  if(!disable_mods_dialog(con, era_name)) {
607  return false;
608  }
609  }
610 
611  std::vector<std::string> newmods = req;
612  for(const std::string& i : mods_) {
613  if(!utils::contains(con, i)) {
614  newmods.push_back(i);
615  }
616  }
617 
618  mods_ = newmods;
619 
620  // Now checking if the currently selected scenario conflicts the era
621  // and changing scenario if necessary
622  if(!conflicts(era, elem(scenario_, "scenario"))) {
623  return true;
624  }
625 
626  std::vector<std::string> compatible;
627  for(const config& i : depinfo_.child_range("scenario")) {
628  if(!conflicts(era, elem(i["id"], "scenario"))) {
629  compatible.push_back(i["id"]);
630  }
631  }
632 
633  if(!compatible.empty()) {
634  scenario_ = change_scenario_dialog(compatible);
635  } else {
636  failure_dialog(_("No compatible scenarios found."));
637  return false;
638  }
639 
640  if(scenario_.empty()) {
641  return false;
642  }
643 
644  return change_scenario(scenario_);
645 }
646 
647 bool manager::change_modifications(const std::vector<std::string>& modifications)
648 {
649  // Checking if the selected combination of mods is valid at all
650  std::vector<std::string> filtered;
651  for(const std::string& i : modifications) {
652  bool ok = true;
653  elem ei(i, "modification");
654 
655  for(const std::string& j : filtered) {
656  ok = ok && !conflicts(ei, elem(j, "modification"));
657  }
658 
659  if(ok) {
660  filtered.push_back(i);
661  }
662  }
663 
664  if(filtered.size() != modifications.size()) {
665  failure_dialog(_("Not all of the chosen modifications are compatible."
666  " Some of them will be disabled."));
667  }
668 
669  mods_ = filtered;
670 
671  // Checking if the currently selected era is compatible with the set
672  // modifications, and changing era if necessary
673  std::vector<std::string> compatible;
674  for(const config& c : depinfo_.child_range("era")) {
675  elem era(c["id"], "era");
676  bool ok = true;
677 
678  for(const std::string& s : mods_) {
679  ok = ok && !conflicts(era, elem(s, "modification"));
680  }
681 
682  if(ok) {
683  compatible.push_back(era.id);
684  }
685  }
686 
687  if(!utils::contains(compatible, era_)) {
688  if(!compatible.empty()) {
689  era_ = change_era_dialog(compatible);
690  } else {
691  failure_dialog(_("No compatible eras found."));
692  return false;
693  }
694 
695  if(era_.empty()) {
696  return false;
697  }
698 
699  if(!change_era(era_)) {
700  return false;
701  }
702  } else {
703  if(!change_era(era_)) {
704  return false;
705  }
706  }
707 
708  compatible.clear();
709 
710  // Checking if the currently selected scenario is compatible with
711  // the set modifications, and changing scenario if necessary
712  for(const config& c : depinfo_.child_range("scenario")) {
713  elem scen(c["id"], "scenario");
714  bool ok = true;
715  for(const std::string& s : mods_) {
716  ok = ok && !conflicts(scen, elem(s, "modification"));
717  }
718 
719  if(ok) {
720  compatible.push_back(scen.id);
721  }
722  }
723 
724  if(!utils::contains(compatible, scenario_)) {
725  if(!compatible.empty()) {
726  scenario_ = change_scenario_dialog(compatible);
727  } else {
728  failure_dialog(_("No compatible scenarios found."));
729  return false;
730  }
731 
732  if(scenario_.empty()) {
733  return false;
734  }
735 
736  return change_scenario(scenario_);
737  } else {
738  if(!change_scenario(scenario_)) {
739  return false;
740  }
741  }
742 
743  return true;
744 }
745 
746 } // namespace depcheck
747 
748 } // 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:149
std::string era()
Definition: game.cpp:696
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
static help_manager manager
The help manager.
Definition: help.cpp:34
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:789
bool has_attribute(config_key_type key) const
Definition: config.cpp:217
logger & info()
Definition: log.cpp:90
child_itors child_range(config_key_type key)
Definition: config.cpp:366
#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:108
const std::vector< std::string > items
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:78
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:49
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:1142
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
const t_string id
static lg::log_domain log_mp_create_depcheck("mp/create/depcheck")
int result() const
Returns the selected item.
std::size_t i
Definition: function.cpp:933
static map_location::DIRECTION s
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:726
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:68
mock_char c
std::string::const_iterator iterator
Definition: tokenizer.hpp:24