The Battle for Wesnoth  1.13.10+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 - 2017 by Andrius Silinskas <silinskas.andrius@gmail.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 "config.hpp"
18 #include "formula/string_utils.hpp"
19 #include "gettext.hpp"
20 #include "log.hpp"
21 #include "mt_rng.hpp"
22 #include "units/types.hpp"
23 
24 #include <algorithm>
25 
26 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
27 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
28 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
29 
30 namespace ng
31 {
32 
33 flg_manager::flg_manager(const std::vector<const config*>& era_factions,
34  const config& side, const bool lock_settings, const bool use_map_settings, const bool saved_game)
35  : era_factions_(era_factions)
36  , side_(side)
37  , saved_game_(saved_game)
38  , has_no_recruits_(get_original_recruits(side_).empty() && side_["previous_recruits"].empty())
39  , faction_lock_(side_["faction_lock"].to_bool(lock_settings) && (use_map_settings || lock_settings))
40  , leader_lock_(side_["leader_lock"].to_bool(lock_settings) && (use_map_settings || lock_settings))
41  , available_factions_()
42  , available_leaders_()
43  , available_genders_()
44  , choosable_factions_()
45  , choosable_leaders_()
46  , choosable_genders_()
47  , current_faction_(nullptr)
48  , current_leader_("null")
49  , current_gender_("null")
50  , default_leader_type_(side_["type"])
51  , default_leader_gender_(side_["gender"])
52  , default_leader_cfg_(nullptr)
53 {
54  const std::string& leader_id = side_["id"];
55  if(!leader_id.empty()) {
56  // Check if leader was carried over and now is in [unit] tag.
57  default_leader_cfg_ = &side_.find_child("unit", "id", leader_id);
58  if(*default_leader_cfg_) {
59  default_leader_type_ = (*default_leader_cfg_)["type"].str();
60  default_leader_gender_ = (*default_leader_cfg_)["gender"].str();
61  } else {
62  default_leader_cfg_ = nullptr;
63  }
64  } else if(default_leader_type_.empty()) {
65  // Find a unit which can recruit.
66  for(const config& side_unit : side_.child_range("unit")) {
67  if(side_unit["canrecruit"].to_bool()) {
68  default_leader_type_ = side_unit["type"].str();
69  default_leader_gender_ = side_unit["gender"].str();
70  default_leader_cfg_ = &side_unit;
71  break;
72  }
73  }
74  }
75 
76  if(!default_leader_type_.empty() && default_leader_type_ != "random") {
77  if(unit_types.find(default_leader_type_) == nullptr) {
78  default_leader_type_.clear();
79  default_leader_gender_.clear();
80  default_leader_cfg_ = nullptr;
81  }
82  }
83 
85 
87 }
88 
90 {
91  assert(index < choosable_factions_.size());
93 
96 }
97 
99 {
100  unsigned index = 0;
101  for(const config* faction : choosable_factions_) {
102  if((*faction)["id"] == id) {
103  set_current_faction(index);
104  return;
105  }
106  index++;
107  }
108 
109  ERR_MP << "Faction '" << id << "' is not available for side " << side_["side"] << " Ignoring";
110 }
111 
113 {
114  assert(index < choosable_leaders_.size());
116 
119 }
120 
122 {
123  assert(index < choosable_genders_.size());
125 }
126 
128 {
129  return (*current_faction_)["random_faction"].to_bool();
130 }
131 
132 // When we use a random mode like "no mirror", "no ally mirror", the list of faction ids to avoid is passed
133 // as an argument. It may be that for some scenario configuration, a strict no mirror assignment is not possible,
134 // because there are too many sides, or some users have forced their faction choices to be matching, etc.
135 // In that case we gracefully continue by ignoring the no mirror rule and assigning as we would have if it were off.
136 // If there is still no options we throw a config error because it means the era is misconfigured.
137 void flg_manager::resolve_random(randomness::mt_rng& rng, const std::vector<std::string>& avoid)
138 {
139  if(is_random_faction()) {
140  std::vector<std::string> faction_choices, faction_excepts;
141 
142  faction_choices = utils::split((*current_faction_)["choices"]);
143  if(faction_choices.size() == 1 && faction_choices.front().empty()) {
144  faction_choices.clear();
145  }
146 
147  faction_excepts = utils::split((*current_faction_)["except"]);
148  if(faction_excepts.size() == 1 && faction_excepts.front().empty()) {
149  faction_excepts.clear();
150  }
151 
152  // Builds the list of factions eligible for choice (non-random factions).
153  std::vector<int> nonrandom_sides;
154  std::vector<int> fallback_nonrandom_sides;
155  for(unsigned int i = 0; i < available_factions_.size(); ++i) {
156  const config& faction = *available_factions_[i];
157 
158  if(faction["random_faction"].to_bool()) {
159  continue;
160  }
161 
162  const std::string& faction_id = faction["id"];
163 
164  if(!faction_choices.empty() && std::find(faction_choices.begin(), faction_choices.end(),
165  faction_id) == faction_choices.end()) {
166  continue;
167  }
168 
169  if(!faction_excepts.empty() && std::find(faction_excepts.begin(), faction_excepts.end(),
170  faction_id) != faction_excepts.end()) {
171  continue;
172  }
173 
174  // This side is consistent with this random faction, remember as a fallback.
175  fallback_nonrandom_sides.push_back(i);
176 
177  if(!avoid.empty() && std::find(avoid.begin(), avoid.end(),
178  faction_id) != avoid.end()) {
179  continue;
180  }
181 
182  // This side is consistent with this random faction, and the avoid factions argument.
183  nonrandom_sides.push_back(i);
184  }
185 
186  if(nonrandom_sides.empty()) {
187  // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
188  nonrandom_sides = fallback_nonrandom_sides;
189  }
190 
191  if(nonrandom_sides.empty()) {
192  throw config::error(_("Only random sides in the current era."));
193  }
194 
195  const int faction_index = nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
197 
200  }
201 
202  if(current_leader_ == "random") {
203  std::vector<std::string> nonrandom_leaders = utils::split((*current_faction_)["random_leader"]);
204  if(nonrandom_leaders.empty()) {
205  for(const std::string& leader : available_leaders_) {
206  if(leader != "random") {
207  nonrandom_leaders.push_back(leader);
208  }
209  }
210  }
211 
212  if(nonrandom_leaders.empty()) {
213  throw config::error(vgettext(
214  "Unable to find a leader type for faction $faction", {{"faction", (*current_faction_)["name"].str()}}));
215  } else {
216  const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
217  current_leader_ = nonrandom_leaders[lchoice];
218 
221  }
222  }
223 
224  // Resolve random genders "very much" like standard unit code.
225  if(current_gender_ == "random") {
227  std::vector<std::string> nonrandom_genders;
228  for(const std::string& gender : available_genders_) {
229  if(gender != "random") {
230  nonrandom_genders.push_back(gender);
231  }
232  }
233 
234  const int gchoice = rng.get_next_random() % nonrandom_genders.size();
235  current_gender_ = nonrandom_genders[gchoice];
236  } else {
237  throw config::error(vgettext("Cannot obtain genders for invalid leader $leader", {{"leader", current_leader_}}));
238  }
239  }
240 }
241 
243 {
244  const config* custom_faction = nullptr;
245  const bool show_custom_faction = get_default_faction(side_)["faction"] == "Custom" || !has_no_recruits_ || faction_lock_;
246 
247  for(const config* faction : era_factions_) {
248  if((*faction)["id"] == "Custom" && !show_custom_faction) {
249 
250  // "Custom" faction should not be available if both
251  // "recruit" and "previous_recruits" lists are empty.
252  // However, it should be available if it was explicitly stated so.
253  custom_faction = faction;
254  continue;
255  }
256 
257  // Add default faction to the top of the list.
258  if(get_default_faction(side_)["faction"] == (*faction)["id"]) {
259  available_factions_.insert(available_factions_.begin(), faction);
260  } else {
261  available_factions_.push_back(faction);
262  }
263  }
264 
265  if(available_factions_.empty() && custom_faction) {
266  available_factions_.push_back(custom_faction);
267  }
268 
269  assert(!available_factions_.empty());
270 
272 }
273 
275 {
276  available_leaders_.clear();
277 
278  if(!default_leader_type_.empty() || !side_["no_leader"].to_bool() || !leader_lock_) {
279 
280  int random_pos = 0;
281  // Add a default leader if there is one.
282  if(!default_leader_type_.empty()) {
284  random_pos = 1;
285  }
286 
287  if(!saved_game_ && !is_random_faction()) {
288  if((*current_faction_)["id"] == "Custom") {
289  // Allow user to choose a leader from any faction.
290  for(const config* f : available_factions_) {
291  if((*f)["id"] != "Random") {
293  }
294  }
295  } else {
297  }
298 
299  // Remove duplicate leaders.
300  std::set<std::string> seen;
301  auto pos = std::remove_if(available_leaders_.begin(), available_leaders_.end(),
302  [&seen](const std::string& s) { return !seen.insert(s).second; }
303  );
304 
305  available_leaders_.erase(pos, available_leaders_.end());
306 
307  if(available_leaders_.size() > 1) {
308  available_leaders_.insert(available_leaders_.begin() + random_pos, "random");
309  }
310  }
311  }
312 
313  // If none of the possible leaders could be determined,
314  // use "null" as an indicator for empty leaders list.
315  if(available_leaders_.empty()) {
316  available_leaders_.push_back("null");
317  }
318 
320 }
321 
323 {
324  available_genders_.clear();
325 
326  if(saved_game_) {
327  std::string gender;
328 
329  for(const config& side_unit : side_.child_range("unit")) {
330  if(current_leader_ == side_unit["type"] && side_unit["canrecruit"].to_bool()) {
331  gender = side_unit["gender"].str();
332  break;
333  }
334  }
335 
336  if(!gender.empty()) {
337  available_genders_.push_back(gender);
338  }
339  } else {
341  if(unit->genders().size() > 1 && !leader_lock_) {
342  available_genders_.push_back("random");
343  }
344 
345  for(unit_race::GENDER gender : unit->genders()) {
346  const std::string gender_str = gender == unit_race::FEMALE
349 
350  // Add default gender to the top of the list.
351  if(default_leader_gender_ == gender_str) {
352  available_genders_.insert(available_genders_.begin(), gender_str);
353  } else {
354  available_genders_.push_back(gender_str);
355  }
356  }
357  }
358  }
359 
360  // If none of the possible genders could be determined,
361  // use "null" as an indicator for empty genders list.
362  if(available_genders_.empty()) {
363  available_genders_.push_back("null");
364  }
365 
367 }
368 
370 {
372 
373  if(faction_lock_) {;
374  const int faction_index = find_suitable_faction();
375  if(faction_index >= 0) {
376  const config* faction = choosable_factions_[faction_index];
377  choosable_factions_.clear();
378  choosable_factions_.push_back(faction);
379  }
380  }
381 
382  // Sort alphabetically, but with the random faction options always first.
383  // Since some eras have multiple random options we can't just assume there is
384  // only one random faction on top of the list.
385  std::sort(choosable_factions_.begin(), choosable_factions_.end(), [](const config* c1, const config* c2) {
386  const config& lhs = *c1;
387  const config& rhs = *c2;
388 
389  // Random factions always first.
390  if(lhs["random_faction"].to_bool() && !rhs["random_faction"].to_bool()) {
391  return true;
392  }
393 
394  if(!lhs["random_faction"].to_bool() && rhs["random_faction"].to_bool()) {
395  return false;
396  }
397 
398  return translation::compare(lhs["name"].str(), rhs["name"].str()) < 0;
399  });
400 }
401 
403 {
405 
406  if(!default_leader_type_.empty() && leader_lock_) {
409 
410  choosable_leaders_.clear();
412  }
413  }
414 
415  // Sort alphabetically, but with the 'random' option always first
416  std::sort(choosable_leaders_.begin() + 1, choosable_leaders_.end(), [](const std::string& str1, const std::string& str2) {
417  return str1 < str2;
418  });
419 }
420 
422 {
424 
425  if(leader_lock_) {
426  std::string default_gender = default_leader_gender_;
427  if(default_gender.empty()) {
428  default_gender = choosable_genders_.front();
429  }
430 
431  if(std::find(available_genders_.begin(), available_genders_.end(), default_gender) != available_genders_.end()) {
432  choosable_genders_.clear();
433  choosable_genders_.push_back(default_gender);
434  }
435  }
436 }
437 
439 {
440  const config::attribute_value& default_faction = get_default_faction(side_)["faction"];
441  auto default_faction_it = std::find_if(choosable_factions_.begin(), choosable_factions_.end(),
442  [&default_faction](const config* faction) {
443  return (*faction)["id"] == default_faction;
444  });
445 
446  if(default_faction_it != choosable_factions_.end()) {
447  set_current_faction(default_faction_it - choosable_factions_.begin());
448  } else {
450  }
451 }
452 
454 {
455  std::vector<std::string> find;
456  std::string search_field;
457 
458  if(const config::attribute_value* f = get_default_faction(side_).get("faction")) {
459  // Choose based on faction.
460  find.push_back(f->str());
461  search_field = "id";
462  } else if(side_["faction_from_recruit"].to_bool()) {
463  // Choose based on recruit.
465  search_field = "recruit";
466  } else if(const config::attribute_value *l = side_.get("leader")) {
467  // Choose based on leader.
468  find.push_back(*l);
469  search_field = "leader";
470  } else {
471  find.push_back("Custom");
472  search_field = "id";
473  }
474 
475  int res = -1, index = 0, best_score = 0;
476  for(const config* faction : choosable_factions_) {
477  int faction_score = 0;
478  for(const std::string& search : find) {
479  for(const std::string& r : utils::split((*faction)[search_field])) {
480  if(r == search) {
481  ++faction_score;
482  break;
483  }
484  }
485  }
486 
487  if(faction_score > best_score) {
488  best_score = faction_score;
489  res = index;
490  }
491 
492  ++index;
493  }
494 
495  return res;
496 }
497 
499 {
500  assert(current_faction_);
501 
503 }
504 
506 {
507  std::vector<std::string> leaders_to_append = utils::split((*faction)["leader"]);
508 
509  available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
510  leaders_to_append.end());
511 }
512 
513 int flg_manager::faction_index(const config& faction) const
514 {
515  const auto it = std::find(choosable_factions_.begin(), choosable_factions_.end(), &faction);
516 
517  assert(it != choosable_factions_.end());
518  return std::distance(choosable_factions_.begin(), it);
519 }
520 
521 int flg_manager::leader_index(const std::string& leader) const
522 {
523  const auto it = std::find(choosable_leaders_.begin(), choosable_leaders_.end(), leader);
524 
525  return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
526 }
527 
528 int flg_manager::gender_index(const std::string& gender) const
529 {
530  const auto it = std::find(choosable_genders_.begin(), choosable_genders_.end(), gender);
531 
532  return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
533 }
534 
536 {
537  int index = leader_index(leader);
538  if(index < 0) {
539  ERR_MP << "Leader '" << leader << "' is not available for side " << side_["side"] << " Ignoring";
540  } else {
541  set_current_leader(index);
542  }
543 }
544 
546 {
547  int index = gender_index(gender);
548  if(index < 0) {
549  ERR_MP << "Gender '" << gender << "' is not available for side " << side_["side"] << " Ignoring";
550  } else {
551  set_current_gender(index);
552  }
553 }
554 
555 std::vector<std::string> flg_manager::get_original_recruits(const config& cfg)
556 {
557  return utils::split(get_default_faction(cfg)["recruit"].str());
558 }
559 
561 {
562  if(const config& df = cfg.child("default_faction")) {
563  return df;
564  } else {
565  return cfg;
566  }
567 }
568 
569 } // end namespace ng
const config * default_leader_cfg_
std::string default_leader_type_
config & child(config_key_type 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:400
static const std::string s_male
Standard string id (not translatable) for MALE.
Definition: race.hpp:26
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
bool is_random_faction()
static const config & get_default_faction(const config &cfg)
This class represents a single unit of a specific type.
Definition: unit.hpp:100
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
Variant for storing WML attributes.
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
child_itors child_range(config_key_type key)
Definition: config.cpp:343
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
const config & side_
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
unit_type_data unit_types
Definition: types.cpp:1455
void set_current_leader(const unsigned index)
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).
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.
A single unit type that the player may recruit.
Definition: types.hpp:43
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:25
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
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:33
void append_leaders_from_faction(const config *faction)
std::vector< const config * > available_factions_
std::vector< const config * > choosable_factions_
int gender_index(const std::string &gender) const
returns -1 if no gender with that name was found
const bool has_no_recruits_
std::string current_leader_
int current_faction_index() const
int find_suitable_faction() const
void set_current_faction(const unsigned index)
Definition: flg_manager.cpp:89
std::string current_gender_
std::vector< std::string > choosable_genders_
static std::vector< std::string > get_original_recruits(const config &cfg)
static map_location::DIRECTION s
bool use_map_settings()
Definition: game.cpp:493
size_t i
Definition: function.cpp:933
#define ERR_MP
Definition: flg_manager.cpp:28
const bool saved_game_
static int sort(lua_State *L)
Definition: ltablib.cpp:411
const std::vector< const config * > & era_factions_
std::string vgettext(const char *msgid, const utils::string_map &symbols)
#define f
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:672
bool find(E event, F functor)
Tests whether an event handler is available.
void update_available_genders()
void update_choosable_genders()
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:1273
std::vector< std::string > available_genders_
void update_available_leaders()
void select_default_faction()
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
std::vector< std::string > available_leaders_
void update_choosable_factions()
std::vector< std::string > choosable_leaders_
const config * current_faction_