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