The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
flg_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2016 by Andrius Silinskas <[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 #include "flg_manager.hpp"
15 
16 #include "config.hpp"
17 #include "formula/string_utils.hpp"
18 #include "gettext.hpp"
19 #include "mt_rng.hpp"
20 #include "units/types.hpp"
21 #include "wml_separators.hpp"
22 #include "log.hpp"
23 
24 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
25 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
26 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
27 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
28 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
29 
30 namespace ng {
31 
32 #ifdef LOW_MEM
33 std::string get_RC_suffix(const std::string&, const int)
34 {
35  return "";
36 }
37 #else
39 {
40  return "~RC(" + unit_color + ">" + color +
41  ")";
42 }
43 #endif
44 
45 
46 flg_manager::flg_manager(const std::vector<const config*>& era_factions,
47  const config& side, const bool lock_settings, const bool use_map_settings,
48  const bool saved_game) :
49  era_factions_(era_factions),
50  side_(side),
51  use_map_settings_(use_map_settings),
52  saved_game_(saved_game),
53  has_no_recruits_(get_original_recruits(side_).empty() && side_["previous_recruits"].empty()),
54  faction_lock_(side_["faction_lock"].to_bool(lock_settings) && use_map_settings),
55  leader_lock_(side_["leader_lock"].to_bool(lock_settings) && use_map_settings),
56  available_factions_(),
57  available_leaders_(),
58  available_genders_(),
59  choosable_factions_(),
60  choosable_leaders_(),
61  choosable_genders_(),
62  current_faction_(nullptr),
63  current_leader_("null"),
64  current_gender_("null"),
65  default_leader_type_(side_["type"]),
66  default_leader_gender_(side_["gender"]),
67  default_leader_cfg_(nullptr)
68 {
69  const std::string& leader_id = side_["id"];
70  if (!leader_id.empty()) {
71  // Check if leader was carried over and now is in [unit] tag.
72  default_leader_cfg_ = &side_.find_child("unit", "id", leader_id);
73  if (*default_leader_cfg_) {
74  default_leader_type_ = (*default_leader_cfg_)["type"].str();
75  default_leader_gender_ = (*default_leader_cfg_)["gender"].str();
76  } else {
77  default_leader_cfg_ = nullptr;
78  }
79  } else if (default_leader_type_.empty()) {
80  // Find a unit which can recruit.
81  for (const config& side_unit : side_.child_range("unit")) {
82  if (side_unit["canrecruit"].to_bool()) {
83  default_leader_type_ = side_unit["type"].str();
84  default_leader_gender_ = side_unit["gender"].str();
85  default_leader_cfg_ = &side_unit;
86  break;
87  }
88  }
89  }
90  if (!default_leader_type_.empty() && default_leader_type_ != "random") {
92  if (unit == nullptr) {
93  default_leader_type_.clear();
94  default_leader_gender_.clear();
95  default_leader_cfg_ = nullptr;
96  }
97  }
98 
100 
102 }
103 
105 {
106 }
107 
109 {
110  assert(index < choosable_factions_.size());
112 
115 }
116 
118 {
119  unsigned index = 0;
120  for (const config* faction : choosable_factions_) {
121  if ((*faction)["id"] == id) {
122  set_current_faction(index);
123  return;
124  }
125  index++;
126  }
127  ERR_MP << "Faction '" << id << "' is not available for side " << side_["side"] << " Ignoring";
128 
129 }
130 
132 {
133  assert(index < choosable_leaders_.size());
135 
138 }
139 
141 {
142  assert(index < choosable_genders_.size());
144 }
145 
147 {
148  std::vector<std::string> leaders;
149  for (const std::string& leader : choosable_leaders_) {
150  const unit_type* unit = unit_types.find(leader);
151  if (unit) {
152  leaders.push_back(IMAGE_PREFIX + unit->image() +
153  get_RC_suffix(unit->flag_rgb(), color) +
154  COLUMN_SEPARATOR + unit->type_name());
155  } else if (leader == "random") {
156  leaders.push_back(IMAGE_PREFIX + random_enemy_picture +
157  COLUMN_SEPARATOR + _("Random"));
158  } else if (leader == "null") {
159  leaders.push_back(utils::unicode_em_dash);
160  } else {
161  leaders.push_back("?");
162  }
163  }
164 
165  combo_leader.enable(leaders.size() > 1 && !saved_game_);
166 
167  combo_leader.set_items(leaders);
168  combo_leader.set_selected(current_leader_index());
169 }
170 
172 {
174 
175  std::vector<std::string> genders;
176  for (const std::string& gender : choosable_genders_) {
177  if (gender == unit_race::s_female || gender == unit_race::s_male) {
178  if (unit) {
179  const unit_type& gender_unit =
180  unit->get_gender_unit_type(gender);
181 
182  std::string gender_name = (gender == unit_race::s_female) ?
183  _("Female ♀") : _("Male ♂");
184  genders.push_back(IMAGE_PREFIX + gender_unit.image() +
185  get_RC_suffix(gender_unit.flag_rgb(), color) +
186  COLUMN_SEPARATOR + gender_name);
187  }
188  } else if (gender == "random") {
189  genders.push_back(IMAGE_PREFIX + random_enemy_picture +
190  COLUMN_SEPARATOR + _("Random"));
191  } else if (gender == "null") {
192  genders.push_back(utils::unicode_em_dash);
193  } else {
194  genders.push_back("?");
195  }
196  }
197 
198  combo_gender.enable(genders.size() > 1 && !saved_game_);
199 
200  combo_gender.set_items(genders);
201  combo_gender.set_selected(current_gender_index());
202 }
203 
205 {
206  return (*current_faction_)["random_faction"].to_bool();
207 }
208 
209 // When we use a random mode like "no mirror", "no ally mirror", the list of
210 // faction ids to avoid is past as an argument.
211 // It may be that for some scenario configuration, a strict no mirror
212 // assignment is not possible, because there are too many sides, or some users
213 // have forced their faction choices to be matching, etc.
214 // In that case we gracefully continue by ignoring the no mirror rule and
215 // assigning as we would have if it were off.
216 // If there is still no options we throw a config error because it means the
217 // era is misconfigured.
218 void flg_manager::resolve_random(rand_rng::mt_rng & rng, const std::vector<std::string> & avoid) {
219  if (is_random_faction()) {
220  std::vector<std::string> faction_choices, faction_excepts;
221 
222  faction_choices = utils::split((*current_faction_)["choices"]);
223  if (faction_choices.size() == 1 && faction_choices.front() == "") {
224  faction_choices.clear();
225  }
226 
227  faction_excepts = utils::split((*current_faction_)["except"]);
228  if (faction_excepts.size() == 1 && faction_excepts.front() == "") {
229  faction_excepts.clear();
230  }
231 
232  // Builds the list of factions eligible for choice
233  // (non-random factions).
234  std::vector<int> nonrandom_sides;
235  std::vector<int> fallback_nonrandom_sides;
236  int num = -1;
237  for (const config* i : available_factions_) {
238  ++num;
239  if (!(*i)["random_faction"].to_bool()) {
240  const std::string& faction_id = (*i)["id"];
241 
242  if (!faction_choices.empty() &&
243  std::find(faction_choices.begin(), faction_choices.end(),
244  faction_id) == faction_choices.end()) {
245  continue;
246  }
247 
248  if (!faction_excepts.empty() &&
249  std::find(faction_excepts.begin(), faction_excepts.end(),
250  faction_id) != faction_excepts.end()) {
251  continue;
252  }
253 
254  fallback_nonrandom_sides.push_back(num); //This side is consistent with this random faction, remember as a fallback.
255 
256  if (!avoid.empty() &&
257  std::find(avoid.begin(), avoid.end(),
258  faction_id) != avoid.end()) {
259  continue;
260  }
261 
262  nonrandom_sides.push_back(num); //This side is consistent with this random faction, and the avoid factions argument.
263  }
264  }
265 
266  if (nonrandom_sides.empty()) {
267  nonrandom_sides = fallback_nonrandom_sides; // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
268  }
269 
270  if (nonrandom_sides.empty()) {
271  throw config::error(_("Only random sides in the current era."));
272  }
273 
274  const int faction_index =
275  nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
276  current_faction_ = available_factions_[faction_index];
277 
280  }
281 
282  if (current_leader_ == "random") {
283  std::vector<std::string> nonrandom_leaders =
284  utils::split((*current_faction_)["random_leader"]);
285  if (nonrandom_leaders.empty()) {
286  for (const std::string& leader : available_leaders_) {
287  if (leader != "random") {
288  nonrandom_leaders.push_back(leader);
289  }
290  }
291  }
292 
293  if (nonrandom_leaders.empty()) {
294  utils::string_map i18n_symbols;
295  i18n_symbols["faction"] = (*current_faction_)["name"];
296  throw config::error(vgettext(
297  "Unable to find a leader type for faction $faction",
298  i18n_symbols));
299  } else {
300  const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
301  current_leader_ = nonrandom_leaders[lchoice];
302 
305  }
306  }
307 
308  // Resolve random genders "very much" like standard unit code.
309  if (current_gender_ == "random") {
311  if (ut) {
312  std::vector<std::string> nonrandom_genders;
313  for (const std::string& gender : available_genders_) {
314  if (gender != "random") {
315  nonrandom_genders.push_back(gender);
316  }
317  }
318 
319  const int gchoice = rng.get_next_random() % nonrandom_genders.size();
320  current_gender_ = nonrandom_genders[gchoice];
321  } else {
322  utils::string_map i18n_symbols;
323  i18n_symbols["leader"] = current_leader_;
324  throw config::error(vgettext(
325  "Cannot obtain genders for invalid leader $leader",
326  i18n_symbols));
327  }
328  }
329 }
330 
332 {
333  const config* custom_faction = nullptr;
334  const bool show_custom_faction = get_default_faction(side_)["faction"] == "Custom" || !has_no_recruits_ || faction_lock_;
335 
336  for (const config* faction : era_factions_) {
337  if ((*faction)["id"] == "Custom" && !show_custom_faction) {
338 
339  // "Custom" faction should not be available if both
340  // "recruit" and "previous_recruits" lists are empty.
341  // However, it should be available if it was explicitly stated so.
342  custom_faction = faction;
343  continue;
344  }
345 
346  // Add default faction to the top of the list.
347  if (get_default_faction(side_)["faction"] == (*faction)["id"]) {
348  available_factions_.insert(available_factions_.begin(), faction);
349  } else {
350  available_factions_.push_back(faction);
351  }
352  }
353  if (available_factions_.empty() && custom_faction) {
354  available_factions_.push_back(custom_faction);
355  }
356 
357  assert(!available_factions_.empty());
358 
360 }
361 
363 {
364  available_leaders_.clear();
365 
366  if (!default_leader_type_.empty() || !side_["no_leader"].to_bool() || !leader_lock_) {
367 
368  int random_pos = 0;
369  // Add a default leader if there is one.
370  if (!default_leader_type_.empty()) {
372  random_pos = 1;
373  }
374 
375  if (!saved_game_) {
376  if (!is_random_faction()) {
377  if ((*current_faction_)["id"] == "Custom") {
378  // Allow user to choose a leader from any faction.
379  for (const config* f : available_factions_) {
380  if ((*f)["id"] != "Random") {
382  }
383  }
384  } else {
386  }
387 
388  // Remove duplicate leaders.
389  std::set<std::string> seen;
390  std::vector<std::string>::iterator walker, modifier;
391  for(walker = available_leaders_.begin(),
392  modifier = available_leaders_.begin();
393  walker != available_leaders_.end(); ++walker) {
394 
395  if (seen.insert(*walker).second) {
396  *modifier++ = *walker;
397  }
398  }
399 
400  available_leaders_.erase(modifier, available_leaders_.end());
401 
402  if (!available_leaders_.empty())
403  available_leaders_.insert(available_leaders_.begin() + random_pos, "random");
404  }
405  }
406  }
407 
408  // If none of the possible leaders could be determined,
409  // use "null" as an indicator for empty leaders list.
410  if (available_leaders_.empty()) {
411  available_leaders_.push_back("null");
412  }
413 
415 }
416 
418 {
419  available_genders_.clear();
420 
421  if (saved_game_) {
422  std::string gender;
423  for (const config& side_unit : side_.child_range("unit")) {
424  if (current_leader_ == side_unit["type"] &&
425  side_unit["canrecruit"].to_bool()) {
426 
427  gender = side_unit["gender"].str();
428  break;
429  }
430  }
431  if (!gender.empty()) {
432  available_genders_.push_back(gender);
433  }
434  } else {
436  if (unit) {
437  if (unit->genders().size() > 1 && !leader_lock_) {
438  available_genders_.push_back("random");
439  }
440 
441  for (unit_race::GENDER gender : unit->genders()) {
442  std::string gender_str;
443  if (gender == unit_race::FEMALE) {
444  gender_str = unit_race::s_female;
445  } else {
446  gender_str = unit_race::s_male;
447  }
448 
449  // Add default gender to the top of the list.
450  if (default_leader_gender_ == gender_str) {
451  available_genders_.insert(available_genders_.begin(),
452  gender_str);
453  } else {
454  available_genders_.push_back(gender_str);
455  }
456  }
457  }
458  }
459 
460  // If none of the possible genders could be determined,
461  // use "null" as an indicator for empty genders list.
462  if (available_genders_.empty()) {
463  available_genders_.push_back("null");
464  }
465 
467 }
468 
470 {
472 
473  if (faction_lock_) {;
474  const int faction_index = find_suitable_faction();
475  if (faction_index >= 0) {
476  const config* faction = choosable_factions_[faction_index];
477  choosable_factions_.clear();
478  choosable_factions_.push_back(faction);
479  }
480  }
481 }
482 
484 {
486 
487  if (!default_leader_type_.empty() && leader_lock_) {
490 
491  choosable_leaders_.clear();
493  }
494  }
495 }
496 
498 {
500 
501  if (leader_lock_) {
502  std::string default_gender = default_leader_gender_;
503  if (default_gender.empty()) {
504  default_gender = choosable_genders_.front();
505  }
506 
508  default_gender) != available_genders_.end()) {
509 
510  choosable_genders_.clear();
511  choosable_genders_.push_back(default_gender);
512  }
513  }
514 }
515 
517 {
518  std::vector<std::string> find;
519  std::string search_field;
520 
521  if (const config::attribute_value *f = get_default_faction(side_).get("faction")) {
522  // Choose based on faction.
523  find.push_back(f->str());
524  search_field = "id";
525  } else if (side_["faction_from_recruit"].to_bool()) {
526  // Choose based on recruit.
528  search_field = "recruit";
529  } else if (const config::attribute_value *l = side_.get("leader")) {
530  // Choose based on leader.
531  find.push_back(*l);
532  search_field = "leader";
533  } else {
534  find.push_back("Custom");
535  search_field = "id";
536  }
537 
538  int res = -1, index = 0, best_score = 0;
539  for (const config *faction : choosable_factions_)
540  {
541  int faction_score = 0;
542  std::vector<std::string> recruit =
543  utils::split((*faction)[search_field]);
544  for (const std::string &search : find) {
545  for (const std::string &r : recruit) {
546  if (r == search) {
547  ++faction_score;
548  break;
549  }
550  }
551  }
552  if (faction_score > best_score) {
553  best_score = faction_score;
554  res = index;
555  }
556  ++index;
557  }
558  return res;
559 }
560 
562 {
563  assert(current_faction_);
564 
566 }
567 
569 {
570  std::vector<std::string> leaders_to_append =
571  utils::split((*faction)["leader"]);
572 
573  available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
574  leaders_to_append.end());
575 }
576 
577 int flg_manager::faction_index(const config& faction) const
578 {
579  std::vector<const config*>::const_iterator it = std::find(
580  choosable_factions_.begin(), choosable_factions_.end(), &faction);
581 
582  assert(it != choosable_factions_.end());
583  return std::distance(choosable_factions_.begin(), it);
584 }
585 
586 int flg_manager::leader_index(const std::string& leader) const
587 {
588  std::vector<std::string>::const_iterator it = std::find(
589  choosable_leaders_.begin(), choosable_leaders_.end(), leader);
590 
591  return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
592 }
593 
594 int flg_manager::gender_index(const std::string& gender) const
595 {
596  std::vector<std::string>::const_iterator it = std::find(
597  choosable_genders_.begin(), choosable_genders_.end(), gender);
598 
599  return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
600 }
601 
603 {
604  int index = leader_index(leader);
605  if (index < 0) {
606  ERR_MP << "Leader '" << leader << "' is not available for side " << side_["side"] << " Ignoring";
607  }
608  else {
609  set_current_leader(index);
610  }
611 }
612 
614 {
615  int index = gender_index(gender);
616  if (index < 0) {
617  ERR_MP << "Gender '" << gender << "' is not available for side " << side_["side"] << " Ignoring";
618  }
619  else {
620  set_current_gender(index);
621  }
622 }
623 
624 std::vector<std::string> flg_manager::get_original_recruits(const config& cfg)
625 {
626  return utils::split(get_default_faction(cfg)["recruit"].str());
627 }
628 
630 {
631  const config& df = cfg.child("default_faction");
632  if (df) {
633  return df;
634  }
635  else {
636  return cfg;
637  }
638 }
639 
640 } // end namespace ng
const config * default_leader_cfg_
child_itors child_range(const std::string &key)
Definition: config.cpp:613
std::string default_leader_type_
static const std::string s_male
Standard string id (not translatable) for MALE.
Definition: race.hpp:27
bool is_random_faction()
static const config & get_default_faction(const config &cfg)
const unit_type & get_gender_unit_type(std::string gender) const
Definition: types.cpp:439
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:112
Definition: unit.hpp:95
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:29
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
virtual void enable(bool new_val=true)
Definition: button.cpp:386
const config & side_
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
unit_type_data unit_types
Definition: types.cpp:1314
void set_current_leader(const unsigned index)
const std::string & image() const
Definition: types.hpp:138
int faction_index(const config &faction) const
void update_available_factions()
void set_current_gender(const unsigned index)
int leader_index(const std::string &leader) const
returns -1 if no leader with that name was found
Definitions for the interface to Wesnoth Markup Language (WML).
Variant for storing WML attributes.
Definition: config.hpp:223
char const IMAGE_PREFIX
GLdouble l
Definition: glew.h:6966
std::string default_leader_gender_
const bool leader_lock_
static const std::string s_female
Standard string id (not translatable) for FEMALE.
Definition: race.hpp:26
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
const std::string & flag_rgb() const
Definition: types.hpp:147
std::map< std::string, t_string > string_map
void reset_leader_combo(gui::combo &combo_leader, const std::string &color) const
int current_gender_index() const
flg_manager(const std::vector< const config * > &era_factions, const config &side, const bool faction_lock, const bool leader_lock, const bool saved_game)
Definition: flg_manager.cpp:46
void append_leaders_from_faction(const config *faction)
std::vector< const config * > available_factions_
std::vector< const config * > choosable_factions_
const std::vector< unit_race::GENDER > & genders() const
The returned vector will not be empty, provided this has been built to the HELP_INDEXED status...
Definition: types.hpp:198
GLuint num
Definition: glew.h:2552
int gender_index(const std::string &gender) const
returns -1 if no gender with that name was found
const std::string unicode_em_dash
const bool has_no_recruits_
void reset_gender_combo(gui::combo &combo_gender, const std::string &color) const
std::string current_leader_
GLuint color
Definition: glew.h:5801
std::string get_RC_suffix(const std::string &unit_color, const std::string &color)
Definition: flg_manager.cpp:38
int current_faction_index() const
int find_suitable_faction() const
GLuint res
Definition: glew.h:9258
void set_current_faction(const unsigned index)
std::string current_gender_
std::vector< std::string > choosable_genders_
void set_selected(int val)
Definition: combo.cpp:68
static std::vector< std::string > get_original_recruits(const config &cfg)
GLuint index
Definition: glew.h:1782
size_t i
Definition: function.cpp:1057
void set_items(const std::vector< std::string > &items)
Definition: combo.cpp:52
#define ERR_MP
Definition: flg_manager.cpp:28
GLdouble GLdouble GLdouble r
Definition: glew.h:1374
const bool saved_game_
const std::vector< const config * > & era_factions_
const std::string random_enemy_picture("units/random-dice.png")
std::string vgettext(const char *msgid, const utils::string_map &symbols)
const attribute_value * get(const std::string &key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:935
char const COLUMN_SEPARATOR
bool find(E event, F functor)
Tests whether an event handler is available.
void update_available_genders()
void update_choosable_genders()
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
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).
const bool faction_lock_
void update_choosable_leaders()
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1155
std::vector< std::string > available_genders_
int current_leader_index() const
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.
void update_available_leaders()
void resolve_random(rand_rng::mt_rng &rng, const std::vector< std::string > &avoid)
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
GLsizei const GLcharARB ** string
Definition: glew.h:4503
std::vector< std::string > available_leaders_
void update_choosable_factions()
std::vector< std::string > choosable_leaders_
GLclampf f
Definition: glew.h:3024
const config * current_faction_