The Battle for Wesnoth  1.15.0-dev
flg_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2018 by Andrius Silinskas <silinskas.andrius@gmail.com>
3  Part of the Battle for Wesnoth Project https://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_num_(side["side"].to_int())
37  , faction_from_recruit_(side["faction_from_recruit"].to_bool())
38  , original_type_(get_default_faction(side)["type"].str())
39  , original_gender_(get_default_faction(side)["gender"].str())
40  , savegame_gender_()
41  , original_faction_(get_default_faction(side)["faction"].str())
42  , original_recruit_(utils::split(get_default_faction(side)["recruit"].str()))
43  , choose_faction_by_leader_(side["leader"].str())
44  , saved_game_(saved_game)
45  , has_no_recruits_(original_recruit_.empty() && side["previous_recruits"].empty())
46  , faction_lock_(side["faction_lock"].to_bool(lock_settings))
47  , leader_lock_(side["leader_lock"].to_bool(lock_settings))
48  , available_factions_()
49  , available_leaders_()
50  , available_genders_()
51  , choosable_factions_()
52  , choosable_leaders_()
53  , choosable_genders_()
54  , current_faction_(nullptr)
55  , current_leader_("null")
56  , current_gender_("null")
57  , default_leader_type_(side["type"])
58  , default_leader_gender_(side["gender"])
59  , default_leader_cfg_(nullptr)
60 {
61  const std::string& leader_id = side["id"];
62  if(!leader_id.empty()) {
63  // Check if leader was carried over and now is in [unit] tag.
64  default_leader_cfg_ = &side.find_child("unit", "id", leader_id);
65  if(*default_leader_cfg_) {
66  default_leader_type_ = (*default_leader_cfg_)["type"].str();
67  default_leader_gender_ = (*default_leader_cfg_)["gender"].str();
68  } else {
69  default_leader_cfg_ = nullptr;
70  }
71  } else if(default_leader_type_.empty()) {
72  // Find a unit which can recruit.
73  for(const config& side_unit : side.child_range("unit")) {
74  if(side_unit["canrecruit"].to_bool()) {
75  default_leader_type_ = side_unit["type"].str();
76  default_leader_gender_ = side_unit["gender"].str();
77  default_leader_cfg_ = &side_unit;
78  break;
79  }
80  }
81  }
82 
83  if(!default_leader_type_.empty() && default_leader_type_ != "random") {
84  if(unit_types.find(default_leader_type_) == nullptr) {
85  default_leader_type_.clear();
86  default_leader_gender_.clear();
87  default_leader_cfg_ = nullptr;
88  }
89  }
90 
91  leader_lock_ = leader_lock_ && (use_map_settings || lock_settings || default_leader_type_.empty());
92  faction_lock_ = faction_lock_ && (use_map_settings || lock_settings);
93 
94  //TODO: this code looks wrong, we should probably just use default_leader_gender_ from above.
95  for(const config& side_unit : side.child_range("unit")) {
96  if(current_leader_ == side_unit["type"] && side_unit["canrecruit"].to_bool()) {
97  savegame_gender_ = side_unit["gender"].str();
98  break;
99  }
100  }
101 
103 
105 
106 }
107 
109 {
110  assert(index < choosable_factions_.size());
112 
115 }
116 
117 void flg_manager::set_current_faction(const std::string& id)
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 
128  ERR_MP << "Faction '" << id << "' is not available for side " << side_num_ << " Ignoring";
129 }
130 
132 {
133  assert(index < choosable_leaders_.size());
135 
138 }
139 
141 {
142  assert(index < choosable_genders_.size());
144 }
145 
147 {
148  return (*current_faction_)["random_faction"].to_bool();
149 }
150 
151 // When we use a random mode like "no mirror", "no ally mirror", the list of faction ids to avoid is passed
152 // as an argument. It may be that for some scenario configuration, a strict no mirror assignment is not possible,
153 // because there are too many sides, or some users have forced their faction choices to be matching, etc.
154 // In that case we gracefully continue by ignoring the no mirror rule and assigning as we would have if it were off.
155 // If there is still no options we throw a config error because it means the era is misconfigured.
156 void flg_manager::resolve_random(randomness::mt_rng& rng, const std::vector<std::string>& avoid)
157 {
158  if(is_random_faction()) {
159  std::vector<std::string> faction_choices, faction_excepts;
160 
161  faction_choices = utils::split((*current_faction_)["choices"]);
162  if(faction_choices.size() == 1 && faction_choices.front().empty()) {
163  faction_choices.clear();
164  }
165 
166  faction_excepts = utils::split((*current_faction_)["except"]);
167  if(faction_excepts.size() == 1 && faction_excepts.front().empty()) {
168  faction_excepts.clear();
169  }
170 
171  // Builds the list of factions eligible for choice (non-random factions).
172  std::vector<int> nonrandom_sides;
173  std::vector<int> fallback_nonrandom_sides;
174  for(unsigned int i = 0; i < available_factions_.size(); ++i) {
175  const config& faction = *available_factions_[i];
176 
177  if(faction["random_faction"].to_bool()) {
178  continue;
179  }
180 
181  const std::string& faction_id = faction["id"];
182 
183  if(!faction_choices.empty() && std::find(faction_choices.begin(), faction_choices.end(),
184  faction_id) == faction_choices.end()) {
185  continue;
186  }
187 
188  if(!faction_excepts.empty() && std::find(faction_excepts.begin(), faction_excepts.end(),
189  faction_id) != faction_excepts.end()) {
190  continue;
191  }
192 
193  // This side is consistent with this random faction, remember as a fallback.
194  fallback_nonrandom_sides.push_back(i);
195 
196  if(!avoid.empty() && std::find(avoid.begin(), avoid.end(),
197  faction_id) != avoid.end()) {
198  continue;
199  }
200 
201  // This side is consistent with this random faction, and the avoid factions argument.
202  nonrandom_sides.push_back(i);
203  }
204 
205  if(nonrandom_sides.empty()) {
206  // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
207  nonrandom_sides = fallback_nonrandom_sides;
208  }
209 
210  if(nonrandom_sides.empty()) {
211  throw config::error(_("Only random sides in the current era."));
212  }
213 
214  const int faction_index = nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
216 
219  }
220 
221  if(current_leader_ == "random") {
222  std::vector<std::string> nonrandom_leaders = utils::split((*current_faction_)["random_leader"]);
223  if(nonrandom_leaders.empty()) {
224  for(const std::string& leader : available_leaders_) {
225  if(leader != "random") {
226  nonrandom_leaders.push_back(leader);
227  }
228  }
229  }
230 
231  if(nonrandom_leaders.empty()) {
232  throw config::error(VGETTEXT(
233  "Unable to find a leader type for faction $faction", {{"faction", (*current_faction_)["name"].str()}}));
234  } else {
235  const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
236  current_leader_ = nonrandom_leaders[lchoice];
237 
240  }
241  }
242 
243  // Resolve random genders "very much" like standard unit code.
244  if(current_gender_ == "random") {
246  std::vector<std::string> nonrandom_genders;
247  for(const std::string& gender : available_genders_) {
248  if(gender != "random") {
249  nonrandom_genders.push_back(gender);
250  }
251  }
252 
253  const int gchoice = rng.get_next_random() % nonrandom_genders.size();
254  current_gender_ = nonrandom_genders[gchoice];
255  } else {
256  throw config::error(VGETTEXT("Cannot obtain genders for invalid leader $leader", {{"leader", current_leader_}}));
257  }
258  }
259 }
260 
262 {
263  const config* custom_faction = nullptr;
264  const bool show_custom_faction = original_faction_ == "Custom" || !has_no_recruits_ || faction_lock_;
265 
266  for(const config* faction : era_factions_) {
267  if((*faction)["id"] == "Custom" && !show_custom_faction) {
268 
269  // "Custom" faction should not be available if both
270  // "recruit" and "previous_recruits" lists are empty.
271  // However, it should be available if it was explicitly stated so.
272  custom_faction = faction;
273  continue;
274  }
275 
276  // Add default faction to the top of the list.
277  if(original_faction_ == (*faction)["id"]) {
278  available_factions_.insert(available_factions_.begin(), faction);
279  } else {
280  available_factions_.push_back(faction);
281  }
282  }
283 
284  if(available_factions_.empty() && custom_faction) {
285  available_factions_.push_back(custom_faction);
286  }
287 
288  assert(!available_factions_.empty());
289 
291 }
292 
294 {
295  available_leaders_.clear();
296 
297  if(!default_leader_type_.empty() || !leader_lock_) {
298 
299  int random_pos = 0;
300  // Add a default leader if there is one.
301  if(!default_leader_type_.empty()) {
303  random_pos = 1;
304  }
305 
306  if(!saved_game_ && !is_random_faction()) {
307  if((*current_faction_)["id"] == "Custom") {
308  // Allow user to choose a leader from any faction.
309  for(const config* f : available_factions_) {
310  if((*f)["id"] != "Random") {
312  }
313  }
314  } else {
316  }
317 
318  // Remove duplicate leaders.
319  std::set<std::string> seen;
320  auto pos = std::remove_if(available_leaders_.begin(), available_leaders_.end(),
321  [&seen](const std::string& s) { return !seen.insert(s).second; }
322  );
323 
324  available_leaders_.erase(pos, available_leaders_.end());
325 
326  if(available_leaders_.size() > 1) {
327  available_leaders_.insert(available_leaders_.begin() + random_pos, "random");
328  }
329  }
330  }
331 
332  // If none of the possible leaders could be determined,
333  // use "null" as an indicator for empty leaders list.
334  if(available_leaders_.empty()) {
335  available_leaders_.push_back("null");
336  }
337 
339 }
340 
342 {
343  available_genders_.clear();
344 
345  if(saved_game_) {
346  if(!savegame_gender_.empty()) {
348  }
349  } else {
351  if(unit->genders().size() > 1 && !leader_lock_) {
352  available_genders_.push_back("random");
353  }
354 
355  for(unit_race::GENDER gender : unit->genders()) {
356  const std::string gender_str = gender == unit_race::FEMALE
359 
360  // Add default gender to the top of the list.
361  if(default_leader_gender_ == gender_str) {
362  available_genders_.insert(available_genders_.begin(), gender_str);
363  } else {
364  available_genders_.push_back(gender_str);
365  }
366  }
367  }
368  }
369 
370  // If none of the possible genders could be determined,
371  // use "null" as an indicator for empty genders list.
372  if(available_genders_.empty()) {
373  available_genders_.push_back("null");
374  }
375 
377 }
378 
380 {
382 
383  if(faction_lock_) {;
384  const int faction_index = find_suitable_faction();
385  if(faction_index >= 0) {
386  const config* faction = choosable_factions_[faction_index];
387  choosable_factions_.clear();
388  choosable_factions_.push_back(faction);
389  }
390  }
391 }
392 
394 {
396 
397  if(!default_leader_type_.empty() && leader_lock_) {
400 
401  choosable_leaders_.clear();
403  }
404  }
405 
406  // Sort alphabetically, but with the 'random' option always first
407  std::sort(choosable_leaders_.begin() + 1, choosable_leaders_.end(), [](const std::string& str1, const std::string& str2) {
408  return str1 < str2;
409  });
410 }
411 
413 {
415 
416  if(leader_lock_) {
417  std::string default_gender = default_leader_gender_;
418  if(default_gender.empty()) {
419  default_gender = choosable_genders_.front();
420  }
421 
422  if(std::find(available_genders_.begin(), available_genders_.end(), default_gender) != available_genders_.end()) {
423  choosable_genders_.clear();
424  choosable_genders_.push_back(default_gender);
425  }
426  }
427 }
428 
430 {
431  const std::string& default_faction = original_faction_;
432  auto default_faction_it = std::find_if(choosable_factions_.begin(), choosable_factions_.end(),
433  [&default_faction](const config* faction) {
434  return (*faction)["id"] == default_faction;
435  });
436 
437  if(default_faction_it != choosable_factions_.end()) {
438  set_current_faction(std::distance(choosable_factions_.begin(), default_faction_it));
439  } else {
441  }
442 }
443 
445 {
446  std::vector<std::string> find;
447  std::string search_field;
448 
449  if(!original_faction_.empty()) {
450  // Choose based on faction.
451  find.push_back(original_faction_);
452  search_field = "id";
453  } else if(faction_from_recruit_) {
454  // Choose based on recruit.
455  find = original_recruit_;
456  search_field = "recruit";
457  } else if(!choose_faction_by_leader_.empty()) {
458  // Choose based on leader.
459  find.push_back(choose_faction_by_leader_);
460  search_field = "leader";
461  } else {
462  find.push_back("Custom");
463  search_field = "id";
464  }
465 
466  int res = -1, index = 0, best_score = 0;
467  for(const config* faction : choosable_factions_) {
468  int faction_score = 0;
469  for(const std::string& search : find) {
470  for(const std::string& r : utils::split((*faction)[search_field])) {
471  if(r == search) {
472  ++faction_score;
473  break;
474  }
475  }
476  }
477 
478  if(faction_score > best_score) {
479  best_score = faction_score;
480  res = index;
481  }
482 
483  ++index;
484  }
485 
486  return res;
487 }
488 
490 {
491  assert(current_faction_);
492 
494 }
495 
497 {
498  std::vector<std::string> leaders_to_append = utils::split((*faction)["leader"]);
499 
500  available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
501  leaders_to_append.end());
502 }
503 
504 int flg_manager::faction_index(const config& faction) const
505 {
506  const auto it = std::find(choosable_factions_.begin(), choosable_factions_.end(), &faction);
507 
508  assert(it != choosable_factions_.end());
509  return std::distance(choosable_factions_.begin(), it);
510 }
511 
512 int flg_manager::leader_index(const std::string& leader) const
513 {
514  const auto it = std::find(choosable_leaders_.begin(), choosable_leaders_.end(), leader);
515 
516  return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
517 }
518 
519 int flg_manager::gender_index(const std::string& gender) const
520 {
521  const auto it = std::find(choosable_genders_.begin(), choosable_genders_.end(), gender);
522 
523  return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
524 }
525 
526 void flg_manager::set_current_leader(const std::string& leader)
527 {
528  int index = leader_index(leader);
529  if(index < 0) {
530  ERR_MP << "Leader '" << leader << "' is not available for side " << side_num_ << " Ignoring";
531  } else {
532  set_current_leader(index);
533  }
534 }
535 
536 void flg_manager::set_current_gender(const std::string& gender)
537 {
538  int index = gender_index(gender);
539  if(index < 0) {
540  ERR_MP << "Gender '" << gender << "' is not available for side " << side_num_ << " Ignoring";
541  } else {
542  set_current_gender(index);
543  }
544 }
545 
547 {
548  if(const config& df = cfg.child("default_faction")) {
549  return df;
550  } else {
551  return cfg;
552  }
553 }
554 
555 } // end namespace ng
const config * default_leader_cfg_
int faction_index(const config &faction) const
std::string default_leader_type_
std::string savegame_gender_
int current_faction_index() const
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:424
static const std::string s_male
Standard string id (not translatable) for MALE.
Definition: race.hpp:28
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:1275
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:99
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:840
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)
child_itors child_range(config_key_type key)
Definition: config.cpp:366
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:62
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
int gender_index(const std::string &gender) const
returns -1 if no gender with that name was found
unit_type_data unit_types
Definition: types.cpp:1452
void set_current_leader(const unsigned index)
void update_available_factions()
void set_current_gender(const unsigned index)
Definitions for the interface to Wesnoth Markup Language (WML).
const std::string original_faction_
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:42
std::string default_leader_gender_
static const std::string s_female
Standard string id (not translatable) for FEMALE.
Definition: race.hpp:27
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
void append_leaders_from_faction(const config *faction)
const std::string choose_faction_by_leader_
flg_manager(const std::vector< const config *> &era_factions, const config &side, bool lock_settings, bool use_map_settings, bool saved_game)
Definition: flg_manager.cpp:33
int leader_index(const std::string &leader) const
returns -1 if no leader with that name was found
std::vector< const config * > available_factions_
std::vector< const config * > choosable_factions_
const bool has_no_recruits_
std::string current_leader_
void set_current_faction(const unsigned index)
std::string current_gender_
int find_suitable_faction() const
std::size_t i
Definition: function.cpp:933
std::vector< std::string > choosable_genders_
static map_location::DIRECTION s
bool use_map_settings()
Definition: game.cpp:491
#define ERR_MP
Definition: flg_manager.cpp:28
const bool saved_game_
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
static int sort(lua_State *L)
Definition: ltablib.cpp:411
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
const std::vector< const config * > & era_factions_
#define f
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 std::vector< std::string > original_recruit_
void update_choosable_leaders()
std::vector< std::string > available_genders_
void update_available_leaders()
const int side_num_
void select_default_faction()
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
const bool faction_from_recruit_
std::vector< std::string > available_leaders_
void update_choosable_factions()
std::vector< std::string > choosable_leaders_
const config * current_faction_