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 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
25 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
26 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
27 
28 namespace ng
29 {
30 
31 flg_manager::flg_manager(const std::vector<const config*>& era_factions,
32  const config& side, const bool lock_settings, const bool use_map_settings, const bool saved_game)
33  : era_factions_(era_factions)
34  , side_(side)
35  , saved_game_(saved_game)
36  , has_no_recruits_(get_original_recruits(side_).empty() && side_["previous_recruits"].empty())
37  , faction_lock_(side_["faction_lock"].to_bool(lock_settings) && (use_map_settings || lock_settings))
38  , leader_lock_(side_["leader_lock"].to_bool(lock_settings) && (use_map_settings || lock_settings))
39  , available_factions_()
40  , available_leaders_()
41  , available_genders_()
42  , choosable_factions_()
43  , choosable_leaders_()
44  , choosable_genders_()
45  , current_faction_(nullptr)
46  , current_leader_("null")
47  , current_gender_("null")
48  , default_leader_type_(side_["type"])
49  , default_leader_gender_(side_["gender"])
50  , default_leader_cfg_(nullptr)
51 {
52  const std::string& leader_id = side_["id"];
53  if(!leader_id.empty()) {
54  // Check if leader was carried over and now is in [unit] tag.
55  default_leader_cfg_ = &side_.find_child("unit", "id", leader_id);
56  if(*default_leader_cfg_) {
57  default_leader_type_ = (*default_leader_cfg_)["type"].str();
58  default_leader_gender_ = (*default_leader_cfg_)["gender"].str();
59  } else {
60  default_leader_cfg_ = nullptr;
61  }
62  } else if(default_leader_type_.empty()) {
63  // Find a unit which can recruit.
64  for(const config& side_unit : side_.child_range("unit")) {
65  if(side_unit["canrecruit"].to_bool()) {
66  default_leader_type_ = side_unit["type"].str();
67  default_leader_gender_ = side_unit["gender"].str();
68  default_leader_cfg_ = &side_unit;
69  break;
70  }
71  }
72  }
73 
74  if(!default_leader_type_.empty() && default_leader_type_ != "random") {
75  if(unit_types.find(default_leader_type_) == nullptr) {
76  default_leader_type_.clear();
77  default_leader_gender_.clear();
78  default_leader_cfg_ = nullptr;
79  }
80  }
81 
83 
85 }
86 
88 {
89  assert(index < choosable_factions_.size());
91 
94 }
95 
97 {
98  unsigned index = 0;
99  for(const config* faction : choosable_factions_) {
100  if((*faction)["id"] == id) {
101  set_current_faction(index);
102  return;
103  }
104  index++;
105  }
106 
107  ERR_MP << "Faction '" << id << "' is not available for side " << side_["side"] << " Ignoring";
108 }
109 
111 {
112  assert(index < choosable_leaders_.size());
114 
117 }
118 
120 {
121  assert(index < choosable_genders_.size());
123 }
124 
126 {
127  return (*current_faction_)["random_faction"].to_bool();
128 }
129 
130 // When we use a random mode like "no mirror", "no ally mirror", the list of faction ids to avoid is passed
131 // as an argument. It may be that for some scenario configuration, a strict no mirror assignment is not possible,
132 // because there are too many sides, or some users have forced their faction choices to be matching, etc.
133 // In that case we gracefully continue by ignoring the no mirror rule and assigning as we would have if it were off.
134 // If there is still no options we throw a config error because it means the era is misconfigured.
135 void flg_manager::resolve_random(randomness::mt_rng& rng, const std::vector<std::string>& avoid)
136 {
137  if(is_random_faction()) {
138  std::vector<std::string> faction_choices, faction_excepts;
139 
140  faction_choices = utils::split((*current_faction_)["choices"]);
141  if(faction_choices.size() == 1 && faction_choices.front().empty()) {
142  faction_choices.clear();
143  }
144 
145  faction_excepts = utils::split((*current_faction_)["except"]);
146  if(faction_excepts.size() == 1 && faction_excepts.front().empty()) {
147  faction_excepts.clear();
148  }
149 
150  // Builds the list of factions eligible for choice (non-random factions).
151  std::vector<int> nonrandom_sides;
152  std::vector<int> fallback_nonrandom_sides;
153  for(unsigned int i = 0; i < available_factions_.size(); ++i) {
154  const config& faction = *available_factions_[i];
155 
156  if(faction["random_faction"].to_bool()) {
157  continue;
158  }
159 
160  const std::string& faction_id = faction["id"];
161 
162  if(!faction_choices.empty() && std::find(faction_choices.begin(), faction_choices.end(),
163  faction_id) == faction_choices.end()) {
164  continue;
165  }
166 
167  if(!faction_excepts.empty() && std::find(faction_excepts.begin(), faction_excepts.end(),
168  faction_id) != faction_excepts.end()) {
169  continue;
170  }
171 
172  // This side is consistent with this random faction, remember as a fallback.
173  fallback_nonrandom_sides.push_back(i);
174 
175  if(!avoid.empty() && std::find(avoid.begin(), avoid.end(),
176  faction_id) != avoid.end()) {
177  continue;
178  }
179 
180  // This side is consistent with this random faction, and the avoid factions argument.
181  nonrandom_sides.push_back(i);
182  }
183 
184  if(nonrandom_sides.empty()) {
185  // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
186  nonrandom_sides = fallback_nonrandom_sides;
187  }
188 
189  if(nonrandom_sides.empty()) {
190  throw config::error(_("Only random sides in the current era."));
191  }
192 
193  const int faction_index = nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
195 
198  }
199 
200  if(current_leader_ == "random") {
201  std::vector<std::string> nonrandom_leaders = utils::split((*current_faction_)["random_leader"]);
202  if(nonrandom_leaders.empty()) {
203  for(const std::string& leader : available_leaders_) {
204  if(leader != "random") {
205  nonrandom_leaders.push_back(leader);
206  }
207  }
208  }
209 
210  if(nonrandom_leaders.empty()) {
211  throw config::error(vgettext(
212  "Unable to find a leader type for faction $faction", {{"faction", (*current_faction_)["name"].str()}}));
213  } else {
214  const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
215  current_leader_ = nonrandom_leaders[lchoice];
216 
219  }
220  }
221 
222  // Resolve random genders "very much" like standard unit code.
223  if(current_gender_ == "random") {
225  std::vector<std::string> nonrandom_genders;
226  for(const std::string& gender : available_genders_) {
227  if(gender != "random") {
228  nonrandom_genders.push_back(gender);
229  }
230  }
231 
232  const int gchoice = rng.get_next_random() % nonrandom_genders.size();
233  current_gender_ = nonrandom_genders[gchoice];
234  } else {
235  throw config::error(vgettext("Cannot obtain genders for invalid leader $leader", {{"leader", current_leader_}}));
236  }
237  }
238 }
239 
241 {
242  const config* custom_faction = nullptr;
243  const bool show_custom_faction = get_default_faction(side_)["faction"] == "Custom" || !has_no_recruits_ || faction_lock_;
244 
245  for(const config* faction : era_factions_) {
246  if((*faction)["id"] == "Custom" && !show_custom_faction) {
247 
248  // "Custom" faction should not be available if both
249  // "recruit" and "previous_recruits" lists are empty.
250  // However, it should be available if it was explicitly stated so.
251  custom_faction = faction;
252  continue;
253  }
254 
255  // Add default faction to the top of the list.
256  if(get_default_faction(side_)["faction"] == (*faction)["id"]) {
257  available_factions_.insert(available_factions_.begin(), faction);
258  } else {
259  available_factions_.push_back(faction);
260  }
261  }
262 
263  if(available_factions_.empty() && custom_faction) {
264  available_factions_.push_back(custom_faction);
265  }
266 
267  assert(!available_factions_.empty());
268 
270 }
271 
273 {
274  available_leaders_.clear();
275 
276  if(!default_leader_type_.empty() || !side_["no_leader"].to_bool() || !leader_lock_) {
277 
278  int random_pos = 0;
279  // Add a default leader if there is one.
280  if(!default_leader_type_.empty()) {
282  random_pos = 1;
283  }
284 
285  if(!saved_game_ && !is_random_faction()) {
286  if((*current_faction_)["id"] == "Custom") {
287  // Allow user to choose a leader from any faction.
288  for(const config* f : available_factions_) {
289  if((*f)["id"] != "Random") {
291  }
292  }
293  } else {
295  }
296 
297  // Remove duplicate leaders.
298  std::set<std::string> seen;
299  auto pos = std::remove_if(available_leaders_.begin(), available_leaders_.end(),
300  [&seen](const std::string& s) { return !seen.insert(s).second; }
301  );
302 
303  available_leaders_.erase(pos, available_leaders_.end());
304 
305  if(available_leaders_.size() > 1) {
306  available_leaders_.insert(available_leaders_.begin() + random_pos, "random");
307  }
308  }
309  }
310 
311  // If none of the possible leaders could be determined,
312  // use "null" as an indicator for empty leaders list.
313  if(available_leaders_.empty()) {
314  available_leaders_.push_back("null");
315  }
316 
318 }
319 
321 {
322  available_genders_.clear();
323 
324  if(saved_game_) {
325  std::string gender;
326 
327  for(const config& side_unit : side_.child_range("unit")) {
328  if(current_leader_ == side_unit["type"] && side_unit["canrecruit"].to_bool()) {
329  gender = side_unit["gender"].str();
330  break;
331  }
332  }
333 
334  if(!gender.empty()) {
335  available_genders_.push_back(gender);
336  }
337  } else {
339  if(unit->genders().size() > 1 && !leader_lock_) {
340  available_genders_.push_back("random");
341  }
342 
343  for(unit_race::GENDER gender : unit->genders()) {
344  const std::string gender_str = gender == unit_race::FEMALE
347 
348  // Add default gender to the top of the list.
349  if(default_leader_gender_ == gender_str) {
350  available_genders_.insert(available_genders_.begin(), gender_str);
351  } else {
352  available_genders_.push_back(gender_str);
353  }
354  }
355  }
356  }
357 
358  // If none of the possible genders could be determined,
359  // use "null" as an indicator for empty genders list.
360  if(available_genders_.empty()) {
361  available_genders_.push_back("null");
362  }
363 
365 }
366 
368 {
370 
371  if(faction_lock_) {;
372  const int faction_index = find_suitable_faction();
373  if(faction_index >= 0) {
374  const config* faction = choosable_factions_[faction_index];
375  choosable_factions_.clear();
376  choosable_factions_.push_back(faction);
377  }
378  }
379 
380  // Sort alphabetically, but with the random faction options always first.
381  // Since some eras have multiple random options we can't just assume there is
382  // only one random faction on top of the list.
383  std::sort(choosable_factions_.begin(), choosable_factions_.end(), [](const config* c1, const config* c2) {
384  const config& lhs = *c1;
385  const config& rhs = *c2;
386 
387  // Random factions always first.
388  if(lhs["random_faction"].to_bool() && !rhs["random_faction"].to_bool()) {
389  return true;
390  }
391 
392  if(!lhs["random_faction"].to_bool() && rhs["random_faction"].to_bool()) {
393  return false;
394  }
395 
396  return translation::compare(lhs["name"].str(), rhs["name"].str()) < 0;
397  });
398 }
399 
401 {
403 
404  if(!default_leader_type_.empty() && leader_lock_) {
407 
408  choosable_leaders_.clear();
410  }
411  }
412 
413  // Sort alphabetically, but with the 'random' option always first
414  std::sort(choosable_leaders_.begin() + 1, choosable_leaders_.end(), [](const std::string& str1, const std::string& str2) {
415  return str1 < str2;
416  });
417 }
418 
420 {
422 
423  if(leader_lock_) {
424  std::string default_gender = default_leader_gender_;
425  if(default_gender.empty()) {
426  default_gender = choosable_genders_.front();
427  }
428 
429  if(std::find(available_genders_.begin(), available_genders_.end(), default_gender) != available_genders_.end()) {
430  choosable_genders_.clear();
431  choosable_genders_.push_back(default_gender);
432  }
433  }
434 }
435 
437 {
438  std::vector<std::string> find;
439  std::string search_field;
440 
441  if(const config::attribute_value* f = get_default_faction(side_).get("faction")) {
442  // Choose based on faction.
443  find.push_back(f->str());
444  search_field = "id";
445  } else if(side_["faction_from_recruit"].to_bool()) {
446  // Choose based on recruit.
448  search_field = "recruit";
449  } else if(const config::attribute_value *l = side_.get("leader")) {
450  // Choose based on leader.
451  find.push_back(*l);
452  search_field = "leader";
453  } else {
454  find.push_back("Custom");
455  search_field = "id";
456  }
457 
458  int res = -1, index = 0, best_score = 0;
459  for(const config* faction : choosable_factions_) {
460  int faction_score = 0;
461  for(const std::string& search : find) {
462  for(const std::string& r : utils::split((*faction)[search_field])) {
463  if(r == search) {
464  ++faction_score;
465  break;
466  }
467  }
468  }
469 
470  if(faction_score > best_score) {
471  best_score = faction_score;
472  res = index;
473  }
474 
475  ++index;
476  }
477 
478  return res;
479 }
480 
482 {
483  assert(current_faction_);
484 
486 }
487 
489 {
490  std::vector<std::string> leaders_to_append = utils::split((*faction)["leader"]);
491 
492  available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
493  leaders_to_append.end());
494 }
495 
496 int flg_manager::faction_index(const config& faction) const
497 {
498  const auto it = std::find(choosable_factions_.begin(), choosable_factions_.end(), &faction);
499 
500  assert(it != choosable_factions_.end());
501  return std::distance(choosable_factions_.begin(), it);
502 }
503 
504 int flg_manager::leader_index(const std::string& leader) const
505 {
506  const auto it = std::find(choosable_leaders_.begin(), choosable_leaders_.end(), leader);
507 
508  return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
509 }
510 
511 int flg_manager::gender_index(const std::string& gender) const
512 {
513  const auto it = std::find(choosable_genders_.begin(), choosable_genders_.end(), gender);
514 
515  return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
516 }
517 
519 {
520  int index = leader_index(leader);
521  if(index < 0) {
522  ERR_MP << "Leader '" << leader << "' is not available for side " << side_["side"] << " Ignoring";
523  } else {
524  set_current_leader(index);
525  }
526 }
527 
529 {
530  int index = gender_index(gender);
531  if(index < 0) {
532  ERR_MP << "Gender '" << gender << "' is not available for side " << side_["side"] << " Ignoring";
533  } else {
534  set_current_gender(index);
535  }
536 }
537 
538 std::vector<std::string> flg_manager::get_original_recruits(const config& cfg)
539 {
540  return utils::split(get_default_faction(cfg)["recruit"].str());
541 }
542 
544 {
545  if(const config& df = cfg.child("default_faction")) {
546  return df;
547  } else {
548  return cfg;
549  }
550 }
551 
552 } // 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:352
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:101
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
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:295
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:31
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:87
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
#define i
#define ERR_MP
Definition: flg_manager.cpp:26
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:624
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()
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_