The Battle for Wesnoth  1.13.4+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 - 2016 by Boldizsár Lipka <[email protected]>
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 
15 #include "depcheck.hpp"
16 
17 #include <algorithm>
18 
19 #include "formula/string_utils.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 
25 #include "gui/dialogs/message.hpp"
26 
27 static lg::log_domain log_mp_create_depcheck("mp/create/depcheck");
28 #define DBG_MP LOG_STREAM(debug, log_mp_create_depcheck)
29 
30 namespace {
31 //helper function
32 void copy_keys(config& out,
33  const config& in,
34  const std::string& type,
35  bool copy_force_key=false)
36 {
37  if (in.has_attribute("allow_" + type)) {
38  out["allow_" + type] = in["allow_" + type];
39  } else if (in.has_attribute("disallow_" + type)) {
40  out["disallow_" + type] = in["disallow_" + type];
41  }
42 
43  if (in.has_attribute("ignore_incompatible_" + type)) {
44  out["ignore_incompatible_" + type] = in["ignore_incompatible_" + type];
45  }
46 
47  if (copy_force_key) {
48  if (in.has_attribute("force_" + type)) {
49  out["force_" + type] = in["force_" + type];
50  }
51  }
52 }
53 } //anonymous namespace
54 
55 namespace ng
56 {
57 
58 namespace depcheck
59 {
60 
61 manager::manager(const config& gamecfg, bool mp, CVideo& video)
62  : video_(video)
63  , depinfo_()
64  , era_()
65  , scenario_()
66  , mods_()
67  , prev_era_()
68  , prev_scenario_()
69  , prev_mods_()
70 {
71  DBG_MP << "Initializing the dependency manager" << std::endl;
72  for (const config& cfg : gamecfg.child_range("modification")) {
73  component_availabilty type = cfg["type"].to_enum<component_availabilty>(component_availabilty::HYBRID);
74  if((type != component_availabilty::MP || mp) && (type != component_availabilty::SP || !mp) ) {
75  config info;
76  info["id"] = cfg["id"];
77  info["name"] = cfg["name"];
78 
79  copy_keys(info, cfg, "scenario");
80  copy_keys(info, cfg, "era");
81  copy_keys(info, cfg, "modification");
82 
83  depinfo_.add_child("modification", info);
84  }
85  }
86 
87  for (const config& cfg : gamecfg.child_range("era")) {
88  component_availabilty type = cfg["type"].to_enum<component_availabilty>(component_availabilty::MP);
89  if((type != component_availabilty::MP || mp) && (type != component_availabilty::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 
116 void manager::save_state()
117 {
118  DBG_MP << "Saving current state" << std::endl;
119  prev_era_ = era_;
120  prev_scenario_ = scenario_;
121  prev_mods_ = mods_;
122 }
123 
124 void manager::revert()
125 {
126  DBG_MP << "Restoring previous state" << std::endl;
127  era_ = prev_era_;
128  scenario_ = prev_scenario_;
129  mods_ = prev_mods_;
130 }
131 
132 bool manager::exists(const elem& e) const
133 {
134  for (const config& cfg : depinfo_.child_range(e.type)) {
135  if (cfg["id"] == e.id) {
136  return true;
137  }
138  }
139 
140  return false;
141 }
142 
143 std::string manager::find_name_for(const elem& e) const
144 {
145  const config& cfg = depinfo_.find_child(e.type, "id", e.id);
146 
147  return cfg["name"];
148 }
149 
150 std::vector<std::string>
151  manager::get_required_not_installed(const elem& e) const
152 {
153  std::vector<std::string> result;
154 
155  std::vector<std::string> items = get_required(e);
156 
157  for (const std::string& str : items) {
158  if (!exists(elem(str, "modification"))) {
159  result.push_back(str);
160  }
161  }
162 
163  return result;
164 }
165 
166 std::vector<std::string> manager::get_required(const elem& e) const
167 {
168  std::vector<std::string> result;
169 
170  if (e.type == "modification") {
171  return result;
172  }
173 
174  config data = depinfo_.find_child(e.type, "id", e.id);
175 
176  if (data.has_attribute("force_modification")) {
177  result = utils::split(data["force_modification"], ',');
178  }
179 
180  return result;
181 }
182 
183 std::vector<std::string> manager::get_required_not_enabled(const elem& e) const
184 {
185  std::vector<std::string> required = get_required(e);
186  std::vector<std::string> result;
187 
188  for (std::string str : required) {
189  if ( !util::contains(mods_, str) ) {
190  result.push_back(str);
191  }
192  }
193 
194  return result;
195 }
196 
197 std::vector<std::string> manager::get_conflicting_enabled(const elem& e) const
198 {
199  std::vector<std::string> result;
200 
201  for (const std::string& mod : mods_) {
202  if (conflicts(elem(mod, "modification"), e)) {
203  result.push_back(mod);
204  }
205  }
206 
207  return result;
208 }
209 
210 bool manager::conflicts(const elem& elem1, const elem& elem2, bool directonly) const
211 {
212  if (elem1 == elem2) {
213  return false;
214  }
215 
216  // We ignore inexistent elements at this point, they will generate
217  // errors in change_era()/change_scenario() anyways.
218  if (!exists(elem1) || !exists(elem2)) {
219  return false;
220  }
221 
222  config data1 = depinfo_.find_child(elem1.type, "id", elem1.id);
223  config data2 = depinfo_.find_child(elem2.type, "id", elem2.id);
224 
225  // Whether we should skip the check entirely
226  if (data1.has_attribute("ignore_incompatible_" + elem2.type)) {
227  std::vector<std::string> ignored =
228  utils::split(data1["ignore_incompatible_" + elem2.type]);
229 
230  if ( util::contains(ignored, elem2.id) ) {
231  return false;
232  }
233  }
234 
235  if (data2.has_attribute("ignore_incompatible_" + elem1.type)) {
236  std::vector<std::string> ignored =
237  utils::split(data2["ignore_incompatible_" + elem1.type]);
238 
239  if ( util::contains(ignored, elem1.id) ) {
240  return false;
241  }
242  }
243 
244  bool result = false;
245 
246  // Checking for direct conflicts between elem1 and elem2
247  if (data1.has_attribute("allow_" + elem2.type)) {
248  std::vector<std::string> allowed =
249  utils::split(data1["allow_" + elem2.type]);
250 
251  result = !util::contains(allowed, elem2.id) && !requires(elem1, elem2);
252  } else if (data1.has_attribute("disallow_" + elem2.type)) {
253  std::vector<std::string> disallowed =
254  utils::split(data1["disallow_" + elem2.type]);
255 
256  result = util::contains(disallowed, elem2.id);
257  }
258 
259  if (data2.has_attribute("allow_" + elem1.type)) {
260  std::vector<std::string> allowed =
261  utils::split(data2["allow_" + elem1.type]);
262 
263  result = result || (!util::contains(allowed, elem1.id) && !requires(elem2, elem1));
264  } else if (data2.has_attribute("disallow_" + elem1.type)) {
265  std::vector<std::string> disallowed =
266  utils::split(data2["disallow_" + elem1.type]);
267 
268  result = result || util::contains(disallowed, elem1.id);
269  }
270 
271  if (result) {
272  return true;
273  }
274 
275  // Checking for indirect conflicts (i.e. conflicts between dependencies)
276  if (!directonly) {
277  std::vector<std::string> req1 = get_required(elem1),
278  req2 = get_required(elem2);
279 
280  for (const std::string& s : req1) {
281  elem m(s, "modification");
282 
283  if (conflicts(elem2, m, true)) {
284  return true;
285  }
286  }
287 
288  for (const std::string& s : req2) {
289  elem m(s, "modification");
290 
291  if (conflicts(elem1, m, true)) {
292  return true;
293  }
294  }
295 
296  for (const std::string& id1 : req1) {
297  elem m1(id1, "modification");
298 
299  for (const std::string& id2 : req2) {
300  elem m2(id2, "modification");
301 
302  if (conflicts(m1, m2)) {
303  return true;
304  }
305  }
306  }
307  }
308 
309  return false;
310 }
311 
312 
313 bool manager::requires(const elem& elem1, const elem& elem2) const
314 {
315  if (elem2.type != "modification") {
316  return false;
317  }
318 
319  config data = depinfo_.find_child(elem1.type, "id", elem1.id);
320 
321  if (data.has_attribute("force_modification")) {
322  std::vector<std::string> required =
323  utils::split(data["force_modification"]);
324 
325  return util::contains(required, elem2.id);
326  }
327 
328  return false;
329 }
330 
331 void manager::try_era(const std::string& id, bool force)
332 {
333  save_state();
334 
335  if (force) {
336  era_ = id;
337  } else if (!change_era(id)) {
338  revert();
339  }
340 }
341 
342 void manager::try_scenario(const std::string& id, bool force)
343 {
344  save_state();
345 
346  if (force) {
347  scenario_ = id;
348  } else if (!change_scenario(id)) {
349  revert();
350  }
351 }
352 
353 void manager::try_modifications(const std::vector<std::string>& ids, bool force)
354 {
355  save_state();
356 
357  if (force) {
358  mods_ = ids;
359  } else if (!change_modifications(ids)) {
360  revert();
361  }
362 }
363 
364 void manager::try_modification_by_index(int index, bool activate, bool force)
365 {
366  std::string id = depinfo_.child("modification", index)["id"];
367  std::vector<std::string> mods_copy = mods_;
368  if (activate) {
369  if (std::find(mods_copy.begin(), mods_copy.end(), id) == mods_copy.end()) {
370  mods_copy.push_back(id);
371  }
372  } else {
373  std::vector<std::string>::iterator pos = std::find(mods_copy.begin(), mods_copy.end(), id);
374  if (pos != mods_copy.end()) {
375  mods_copy.erase(pos);
376  }
377  }
378 
379  try_modifications(mods_copy, force);
380 }
381 
382 void manager::try_era_by_index(int index, bool force)
383 {
384  try_era(depinfo_.child("era", index)["id"], force);
385 }
386 
387 void manager::try_scenario_by_index(int index, bool force)
388 {
389  try_scenario(depinfo_.child("scenario", index)["id"], force);
390 }
391 
392 int manager::get_era_index() const
393 {
394  int result = 0;
395  for (const config& i : depinfo_.child_range("era"))
396  {
397  if (i["id"] == era_) {
398  return result;
399  }
400  result++;
401  }
402 
403  return -1;
404 }
405 
406 int manager::get_scenario_index() const
407 {
408  int result = 0;
409 
410  for (const config& i : depinfo_.child_range("scenario"))
411  {
412  if (i["id"] == scenario_) {
413  return result;
414  }
415  result++;
416  }
417 
418  return -1;
419 }
420 
421 bool manager::is_modification_active(int index) const
422 {
423  std::string id = depinfo_.child("modification", index)["id"];
424  return std::find(mods_.begin(), mods_.end(), id) != mods_.end();
425 }
426 
427 
428 
429 bool manager::enable_mods_dialog(const std::vector<std::string>& mods,
430  const std::string& requester)
431 {
432  std::vector<std::string> items;
433  for (const std::string& mod : mods) {
434  items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
435  }
436 
437  gui2::tdepcheck_confirm_change dialog(true, items, requester);
438  return dialog.show(video_);
439 }
440 
441 bool manager::disable_mods_dialog(const std::vector<std::string>& mods,
442  const std::string& requester)
443 {
444  std::vector<std::string> items;
445  for (const std::string& mod : mods) {
446  items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
447  }
448 
449  gui2::tdepcheck_confirm_change dialog(false, items, requester);
450  return dialog.show(video_);
451 }
452 
453 std::string manager::change_era_dialog(const std::vector<std::string>& eras)
454 {
455  std::vector<std::string> items;
456  for(const std::string& era : eras) {
457  items.push_back(depinfo_.find_child("era", "id", era)["name"]);
458  }
459 
460  gui2::tdepcheck_select_new dialog(ERA, items);
461 
462  if (dialog.show(video_)) {
463  return eras[dialog.result()];
464  }
465 
466  return "";
467 }
468 
470  manager::change_scenario_dialog(const std::vector<std::string>& scenarios)
471 {
472  std::vector<std::string> items;
473  for (const std::string& scenario : scenarios) {
474  items.push_back(depinfo_.find_child("scenario", "id", scenario)["name"]);
475  }
476 
477  gui2::tdepcheck_select_new dialog(SCENARIO, items);
478  if (dialog.show(video_)) {
479  return scenarios[dialog.result()];
480  }
481 
482  return "";
483 }
484 
485 void manager::failure_dialog(const std::string& msg)
486 {
488  (video_, _("Failed to resolve dependencies"), msg, _("OK"));
489 }
490 
491 
492 void manager::insert_element(component_type type, const config& data, int index)
493 {
494  std::string type_str;
495  switch (type) {
496  case ERA:
497  type_str = "era";
498  break;
499  case SCENARIO:
500  type_str = "scenario";
501  break;
502  case MODIFICATION:
503  type_str = "modification";
504  }
505 
506  depinfo_.add_child_at(type_str, data, index);
507 }
508 
509 bool manager::change_scenario(const std::string& id)
510 {
511  // Checking for missing dependencies
512  if (!get_required_not_installed(elem(id, "scenario")).empty()) {
513  std::string msg =
514  _("Scenario can't be activated. Some dependencies are missing: ");
515 
516  msg +=
517  utils::join(get_required_not_installed(elem(id, "scenario")), ", ");
518 
519  failure_dialog(msg);
520  return false;
521  }
522 
523  scenario_ = id;
524 
525  elem scen = elem(id, "scenario");
526  std::string scen_name = find_name_for(scen);
527 
528  // Firstly, we check if we have to enable/disable any mods
529  std::vector<std::string> req = get_required_not_enabled(scen);
530  std::vector<std::string> con = get_conflicting_enabled(scen);
531 
532  if (!req.empty()) {
533  if (!enable_mods_dialog(req, scen_name)) {
534  return false;
535  }
536  }
537 
538  if (!con.empty()) {
539  if (!disable_mods_dialog(con, scen_name)) {
540  return false;
541  }
542  }
543 
544  std::vector<std::string> newmods = req;
545  for (const std::string& i : mods_) {
546  if ( !util::contains(con, i) ) {
547  newmods.push_back(i);
548  }
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 dependenciess
584  if (!get_required_not_installed(elem(id, "era")).empty()) {
585  std::string msg =
586  _("Era can't be activated. Some dependencies are missing: ");
587 
588  msg += utils::join(get_required_not_installed(elem(id, "era")), ", ");
589  failure_dialog(msg);
590  return false;
591  }
592 
593  era_ = id;
594 
595  elem era = elem(id, "era");
596  std::string era_name = find_name_for(era);
597 
598  std::vector<std::string> req = get_required_not_enabled(era);
599  std::vector<std::string> con = get_conflicting_enabled(era);
600 
601  // Firstly, we check if we have to enable/disable any mods
602  if (!req.empty()) {
603  if (!enable_mods_dialog(req, era_name)) {
604  return false;
605  }
606  }
607 
608  if (!con.empty()) {
609  if (!disable_mods_dialog(con, era_name)) {
610  return false;
611  }
612  }
613 
614  std::vector<std::string> newmods = req;
615  for (const std::string& i : mods_) {
616  if ( !util::contains(con, i) ) {
617  newmods.push_back(i);
618  }
619  }
620 
621  mods_ = newmods;
622 
623  // Now checking if the currently selected scenario conflicts the era
624  // and changing scenario if necessary
625  if (!conflicts(era, elem(scenario_, "scenario"))) {
626  return true;
627  }
628 
629  std::vector<std::string> compatible;
630  for (const config& i : depinfo_.child_range("scenario")) {
631  if (!conflicts(era, elem(i["id"], "scenario"))) {
632  compatible.push_back(i["id"]);
633  }
634  }
635 
636  if (!compatible.empty()) {
637  scenario_ = change_scenario_dialog(compatible);
638  } else {
639  failure_dialog(_("No compatible scenarios found."));
640  return false;
641  }
642 
643  if (scenario_.empty()) {
644  return false;
645  }
646 
647  return change_scenario(scenario_);
648 }
649 
650 bool manager::change_modifications
651  (const std::vector<std::string>& modifications)
652 {
653  // Checking if the selected combination of mods is valid at all
654  std::vector<std::string> filtered;
655  for (const std::string& i : modifications) {
656  bool ok = true;
657  elem ei(i, "modification");
658  for (const std::string& j : filtered) {
659  ok = ok && !conflicts(ei, elem(j, "modification"));
660  }
661 
662  if (ok) {
663  filtered.push_back(i);
664  }
665  }
666 
667  if (filtered.size() != modifications.size()) {
668  failure_dialog(_("Not all of the chosen modifications are compatible." \
669  " Some of them will be disabled."));
670  }
671 
672  mods_ = filtered;
673 
674  // Checking if the currently selected era is compatible with the set
675  // modifications, and changing era if necessary
676  std::vector<std::string> compatible;
677  for (const config& c : depinfo_.child_range("era")) {
678  elem era(c["id"], "era");
679  bool ok = true;
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 ( !util::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 ( !util::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 
749 } //namespace depcheck
750 
751 } //namespace ng
child_itors child_range(const std::string &key)
Definition: config.cpp:613
std::string era()
const GLfloat * c
Definition: glew.h:12741
int pos
Definition: formula.cpp:800
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: util.hpp:489
GLuint GLuint GLsizei GLenum type
Definition: glew.h:1221
logger & info()
Definition: log.cpp:91
Definition: video.hpp:58
#define DBG_MP
Definition: depcheck.cpp:28
const std::vector< std::string > items
GLint GLenum GLsizei GLint GLsizei const GLvoid * data
Definition: glew.h:1347
GLuint in
Definition: glew.h:9261
This module controls the multiplayer lobby.
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: image.cpp:1187
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
GLuint64EXT * result
Definition: glew.h:10727
GLuint id
Definition: glew.h:1647
static lg::log_domain log_mp_create_depcheck("mp/create/depcheck")
void show_message(CVideo &video, const std::string &title, const std::string &message, const std::string &button_caption, const bool auto_close, const bool message_use_markup)
Shows a message to the user.
Definition: message.cpp:143
std::string join(T const &v, const std::string &s=",")
Generates a new string joining container items in a list.
GLuint index
Definition: glew.h:1782
size_t i
Definition: function.cpp:1057
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:112
const std::vector< std::string > & modifications(bool mp)
bool has_attribute(const std::string &key) const
Definition: config.cpp:514
const GLdouble * m
Definition: glew.h:6968
bool find(E event, F functor)
Tests whether an event handler is available.
config & find_child(const std::string &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:1010
Standard logging facilities (interface).
std::vector< std::string > split(std::string const &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
#define e
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
GLdouble s
Definition: glew.h:1358
const GLuint * ids
Definition: glew.h:1652
GLsizei const GLcharARB ** string
Definition: glew.h:4503