The Battle for Wesnoth  1.19.5+dev
flg_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2024
3  by Andrius Silinskas <silinskas.andrius@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
17 
18 #include "config.hpp"
19 #include "formula/string_utils.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 #include "mt_rng.hpp"
23 #include "units/types.hpp"
24 #include "utils/general.hpp"
25 
26 #include <algorithm>
27 
28 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
29 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
30 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
31 
32 namespace ng
33 {
34 
35 flg_manager::flg_manager(const std::vector<const config*>& era_factions,
36  const config& side, const bool lock_settings, const bool use_map_settings, const bool saved_game)
37  : era_factions_(era_factions)
38  , side_num_(side["side"].to_int())
39  , faction_from_recruit_(side["faction_from_recruit"].to_bool())
40  , original_type_(get_default_faction(side)["type"].str())
41  , original_gender_(get_default_faction(side)["gender"].str())
42  , savegame_gender_()
43  , original_faction_(get_default_faction(side)["faction"].str())
44  , original_recruit_(utils::split(get_default_faction(side)["recruit"].str()))
45  , choose_faction_by_leader_(side["leader"].str())
46  , saved_game_(saved_game)
47  , has_no_recruits_(original_recruit_.empty() && side["previous_recruits"].empty())
48  , faction_lock_(side["faction_lock"].to_bool(lock_settings))
49  , leader_lock_(side["leader_lock"].to_bool(lock_settings))
50  , available_factions_()
51  , available_leaders_()
52  , available_genders_()
53  , choosable_factions_()
54  , choosable_leaders_()
55  , choosable_genders_()
56  , current_faction_(nullptr)
57  , current_leader_("null")
58  , current_gender_("null")
59  , default_leader_type_(side["type"])
60  , default_leader_gender_(side["gender"])
61  , default_leader_cfg_(nullptr)
62 {
63  const std::string& leader_id = side["id"];
64  if(!leader_id.empty()) {
65  // Check if leader was carried over and now is in [unit] tag.
66  default_leader_cfg_ = side.find_child("unit", "id", leader_id).ptr();
68  default_leader_type_ = (*default_leader_cfg_)["type"].str();
69  default_leader_gender_ = (*default_leader_cfg_)["gender"].str();
70  } else {
71  default_leader_cfg_ = nullptr;
72  }
73  } else if(default_leader_type_.empty()) {
74  // Find a unit which can recruit.
75  for(const config& side_unit : side.child_range("unit")) {
76  if(side_unit["canrecruit"].to_bool()) {
77  default_leader_type_ = side_unit["type"].str();
78  default_leader_gender_ = side_unit["gender"].str();
79  default_leader_cfg_ = &side_unit;
80  break;
81  }
82  }
83  }
84 
85  if(!default_leader_type_.empty() && default_leader_type_ != "random") {
86  if(unit_types.find(default_leader_type_) == nullptr) {
87  default_leader_type_.clear();
88  default_leader_gender_.clear();
89  default_leader_cfg_ = nullptr;
90  }
91  }
92 
93  leader_lock_ = leader_lock_ && (use_map_settings || lock_settings || default_leader_type_.empty());
94  faction_lock_ = faction_lock_ && (use_map_settings || lock_settings);
95 
96  //TODO: this code looks wrong, we should probably just use default_leader_gender_ from above.
97  for(const config& side_unit : side.child_range("unit")) {
98  if(current_leader_ == side_unit["type"] && side_unit["canrecruit"].to_bool()) {
99  savegame_gender_ = side_unit["gender"].str();
100  break;
101  }
102  }
103 
105 
107 
108 }
109 
111 {
112  assert(index < choosable_factions_.size());
114 
117 }
118 
119 void flg_manager::set_current_faction(const std::string& id)
120 {
121  unsigned index = 0;
122  for(const config* faction : choosable_factions_) {
123  if((*faction)["id"] == id) {
125  return;
126  }
127  index++;
128  }
129 
130  ERR_MP << "Faction '" << id << "' is not available for side " << side_num_ << " Ignoring";
131 }
132 
134 {
135  assert(index < choosable_leaders_.size());
137 
140 }
141 
143 {
144  assert(index < choosable_genders_.size());
146 }
147 
149 {
150  return (*current_faction_)["random_faction"].to_bool();
151 }
152 
153 // When we use a random mode like "no mirror", "no ally mirror", the list of faction ids to avoid is passed
154 // as an argument. It may be that for some scenario configuration, a strict no mirror assignment is not possible,
155 // because there are too many sides, or some users have forced their faction choices to be matching, etc.
156 // In that case we gracefully continue by ignoring the no mirror rule and assigning as we would have if it were off.
157 // If there is still no options we throw a config error because it means the era is misconfigured.
158 void flg_manager::resolve_random(randomness::mt_rng& rng, const std::vector<std::string>& avoid)
159 {
160  if(is_random_faction()) {
161  std::vector<std::string> faction_choices, faction_excepts;
162 
163  faction_choices = utils::split((*current_faction_)["choices"]);
164  if(faction_choices.size() == 1 && faction_choices.front().empty()) {
165  faction_choices.clear();
166  }
167 
168  faction_excepts = utils::split((*current_faction_)["except"]);
169  if(faction_excepts.size() == 1 && faction_excepts.front().empty()) {
170  faction_excepts.clear();
171  }
172 
173  // Builds the list of factions eligible for choice (non-random factions).
174  std::vector<int> nonrandom_sides;
175  std::vector<int> fallback_nonrandom_sides;
176  for(unsigned int i = 0; i < available_factions_.size(); ++i) {
177  const config& faction = *available_factions_[i];
178 
179  if(faction["random_faction"].to_bool()) {
180  continue;
181  }
182 
183  const std::string& faction_id = faction["id"];
184 
185  if(!faction_choices.empty() && std::find(faction_choices.begin(), faction_choices.end(),
186  faction_id) == faction_choices.end()) {
187  continue;
188  }
189 
190  if(!faction_excepts.empty() && std::find(faction_excepts.begin(), faction_excepts.end(),
191  faction_id) != faction_excepts.end()) {
192  continue;
193  }
194 
195  // This side is consistent with this random faction, remember as a fallback.
196  fallback_nonrandom_sides.push_back(i);
197 
198  if(!avoid.empty() && std::find(avoid.begin(), avoid.end(),
199  faction_id) != avoid.end()) {
200  continue;
201  }
202 
203  // This side is consistent with this random faction, and the avoid factions argument.
204  nonrandom_sides.push_back(i);
205  }
206 
207  if(nonrandom_sides.empty()) {
208  // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
209  nonrandom_sides = fallback_nonrandom_sides;
210  }
211 
212  if(nonrandom_sides.empty()) {
213  throw config::error(_("Only random sides in the current era."));
214  }
215 
216  const int faction_index = nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
218 
221  }
222 
223  if(current_leader_ == "random") {
224  std::vector<std::string> nonrandom_leaders = utils::split((*current_faction_)["random_leader"]);
225  if(nonrandom_leaders.empty()) {
226  for(const std::string& leader : available_leaders_) {
227  if(leader != "random") {
228  nonrandom_leaders.push_back(leader);
229  }
230  }
231  }
232 
233  if(nonrandom_leaders.empty()) {
234  throw config::error(VGETTEXT(
235  "Unable to find a leader type for faction $faction", {{"faction", (*current_faction_)["name"].str()}}));
236  } else {
237  const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
238  current_leader_ = nonrandom_leaders[lchoice];
239 
242  }
243  }
244 
245  // Resolve random genders "very much" like standard unit code.
246  if(current_gender_ == "random") {
248  std::vector<std::string> nonrandom_genders;
249  for(const std::string& gender : available_genders_) {
250  if(gender != "random") {
251  nonrandom_genders.push_back(gender);
252  }
253  }
254 
255  const int gchoice = rng.get_next_random() % nonrandom_genders.size();
256  current_gender_ = nonrandom_genders[gchoice];
257  } else {
258  throw config::error(VGETTEXT("Cannot obtain genders for invalid leader $leader", {{"leader", current_leader_}}));
259  }
260  }
261 }
262 
264 {
265  const config* custom_faction = nullptr;
266  const bool show_custom_faction = original_faction_ == "Custom" || !has_no_recruits_ || faction_lock_;
267 
268  for(const config* faction : era_factions_) {
269  if((*faction)["id"] == "Custom" && !show_custom_faction) {
270 
271  // "Custom" faction should not be available if both
272  // "recruit" and "previous_recruits" lists are empty.
273  // However, it should be available if it was explicitly stated so.
274  custom_faction = faction;
275  continue;
276  }
277 
278  // Add default faction to the top of the list.
279  if(original_faction_ == (*faction)["id"]) {
280  available_factions_.insert(available_factions_.begin(), faction);
281  } else {
282  available_factions_.push_back(faction);
283  }
284  }
285 
286  if(available_factions_.empty() && custom_faction) {
287  available_factions_.push_back(custom_faction);
288  }
289 
290  assert(!available_factions_.empty());
291 
293 }
294 
296 {
297  available_leaders_.clear();
298 
299  if(!default_leader_type_.empty() || !leader_lock_) {
300 
301  int random_pos = 0;
302  // Add a default leader if there is one.
303  if(!default_leader_type_.empty()) {
305  random_pos = 1;
306  }
307 
308  if(!saved_game_ && !is_random_faction()) {
309  if((*current_faction_)["id"] == "Custom") {
310  // Allow user to choose a leader from any faction.
311  for(const config* f : available_factions_) {
312  if((*f)["id"] != "Random") {
314  }
315  }
316  } else {
318  }
319 
320  // Remove duplicate leaders.
321  std::set<std::string> seen;
322  utils::erase_if(available_leaders_, [&seen](const std::string& s) { return !seen.insert(s).second; });
323 
324  if(available_leaders_.size() > 1) {
325  available_leaders_.insert(available_leaders_.begin() + random_pos, "random");
326  }
327  }
328  }
329 
330  // If none of the possible leaders could be determined,
331  // use "null" as an indicator for empty leaders list.
332  if(available_leaders_.empty()) {
333  available_leaders_.push_back("null");
334  }
335 
337 }
338 
340 {
341  available_genders_.clear();
342 
343  if(saved_game_) {
344  if(!savegame_gender_.empty()) {
346  }
347  } else {
349  if(unit->genders().size() > 1 && !leader_lock_) {
350  available_genders_.push_back("random");
351  }
352 
353  for(unit_race::GENDER gender : unit->genders()) {
354  const std::string gender_str = gender == unit_race::FEMALE
357 
358  // Add default gender to the top of the list.
359  if(default_leader_gender_ == gender_str) {
360  available_genders_.insert(available_genders_.begin(), gender_str);
361  } else {
362  available_genders_.push_back(gender_str);
363  }
364  }
365  }
366  }
367 
368  // If none of the possible genders could be determined,
369  // use "null" as an indicator for empty genders list.
370  if(available_genders_.empty()) {
371  available_genders_.push_back("null");
372  }
373 
375 }
376 
378 {
380 
381  if(faction_lock_) {;
382  const int faction_index = find_suitable_faction();
383  if(faction_index >= 0) {
384  const config* faction = choosable_factions_[faction_index];
385  choosable_factions_.clear();
386  choosable_factions_.push_back(faction);
387  }
388  }
389 }
390 
392 {
394 
395  if(!default_leader_type_.empty() && leader_lock_) {
396  if(std::find(available_leaders_.begin(), available_leaders_.end(),
398 
399  choosable_leaders_.clear();
401  }
402  }
403 
404  // Sort alphabetically, but with the 'random' option always first
405  std::sort(choosable_leaders_.begin() + 1, choosable_leaders_.end(), [](const std::string& str1, const std::string& str2) {
406  return str1 < str2;
407  });
408 }
409 
411 {
413 
414  if(leader_lock_) {
415  std::string default_gender = default_leader_gender_;
416  if(default_gender.empty()) {
417  default_gender = choosable_genders_.front();
418  }
419 
420  if(std::find(available_genders_.begin(), available_genders_.end(), default_gender) != available_genders_.end()) {
421  choosable_genders_.clear();
422  choosable_genders_.push_back(default_gender);
423  }
424  }
425 }
426 
428 {
429  const std::string& default_faction = original_faction_;
430  auto default_faction_it = std::find_if(choosable_factions_.begin(), choosable_factions_.end(),
431  [&default_faction](const config* faction) {
432  return (*faction)["id"] == default_faction;
433  });
434 
435  if(default_faction_it != choosable_factions_.end()) {
436  set_current_faction(std::distance(choosable_factions_.begin(), default_faction_it));
437  } else {
439  }
440 }
441 
443 {
444  std::vector<std::string> find;
445  std::string search_field;
446 
447  if(!original_faction_.empty()) {
448  // Choose based on faction.
449  find.push_back(original_faction_);
450  search_field = "id";
451  } else if(faction_from_recruit_) {
452  // Choose based on recruit.
453  find = original_recruit_;
454  search_field = "recruit";
455  } else if(!choose_faction_by_leader_.empty()) {
456  // Choose based on leader.
457  find.push_back(choose_faction_by_leader_);
458  search_field = "leader";
459  } else {
460  find.push_back("Custom");
461  search_field = "id";
462  }
463 
464  int res = -1, index = 0, best_score = 0;
465  for(const config* faction : choosable_factions_) {
466  int faction_score = 0;
467  for(const std::string& search : find) {
468  for(const std::string& r : utils::split((*faction)[search_field])) {
469  if(r == search) {
470  ++faction_score;
471  break;
472  }
473  }
474  }
475 
476  if(faction_score > best_score) {
477  best_score = faction_score;
478  res = index;
479  }
480 
481  ++index;
482  }
483 
484  return res;
485 }
486 
488 {
489  assert(current_faction_);
490 
492 }
493 
495 {
496  std::vector<std::string> leaders_to_append = utils::split((*faction)["leader"]);
497 
498  available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
499  leaders_to_append.end());
500 }
501 
502 int flg_manager::faction_index(const config& faction) const
503 {
504  const auto it = std::find(choosable_factions_.begin(), choosable_factions_.end(), &faction);
505 
506  assert(it != choosable_factions_.end());
507  return std::distance(choosable_factions_.begin(), it);
508 }
509 
510 int flg_manager::leader_index(const std::string& leader) const
511 {
512  const auto it = std::find(choosable_leaders_.begin(), choosable_leaders_.end(), leader);
513 
514  return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
515 }
516 
517 int flg_manager::gender_index(const std::string& gender) const
518 {
519  const auto it = std::find(choosable_genders_.begin(), choosable_genders_.end(), gender);
520 
521  return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
522 }
523 
524 void flg_manager::set_current_leader(const std::string& leader)
525 {
526  int index = leader_index(leader);
527  if(index < 0) {
528  ERR_MP << "Leader '" << leader << "' is not available for side " << side_num_ << " Ignoring";
529  } else {
531  }
532 }
533 
534 void flg_manager::set_current_gender(const std::string& gender)
535 {
536  int index = gender_index(gender);
537  if(index < 0) {
538  ERR_MP << "Gender '" << gender << "' is not available for side " << side_num_ << " Ignoring";
539  } else {
541  }
542 }
543 
545 {
546  if(auto df = cfg.optional_child("default_faction")) {
547  return *df;
548  } else {
549  return cfg;
550  }
551 }
552 
553 } // end namespace ng
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
optional_config_impl< 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:784
child_itors child_range(config_key_type key)
Definition: config.cpp:272
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
void update_available_factions()
const bool faction_from_recruit_
void update_choosable_leaders()
void update_choosable_factions()
std::string default_leader_type_
void update_available_leaders()
const std::vector< std::string > original_recruit_
std::string current_leader_
const bool has_no_recruits_
void update_available_genders()
void set_current_faction(const unsigned index)
void select_default_faction()
void resolve_random(randomness::mt_rng &rng, const std::vector< std::string > &avoid)
std::vector< std::string > choosable_leaders_
std::vector< std::string > choosable_genders_
std::vector< std::string > available_genders_
void update_choosable_genders()
const config * current_faction_
int faction_index(const config &faction) const
const std::string choose_faction_by_leader_
const std::string original_faction_
void set_current_leader(const unsigned index)
int leader_index(const std::string &leader) const
returns -1 if no leader with that name was found
int gender_index(const std::string &gender) const
returns -1 if no gender with that name was found
int current_faction_index() const
static const config & get_default_faction(const config &cfg)
const bool saved_game_
std::string default_leader_gender_
std::vector< const config * > available_factions_
std::vector< const config * > choosable_factions_
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:35
void set_current_gender(const unsigned index)
bool is_random_faction()
std::string savegame_gender_
void append_leaders_from_faction(const config *faction)
const config * default_leader_cfg_
const std::vector< const config * > & era_factions_
std::string current_gender_
int find_suitable_faction() const
const int side_num_
std::vector< std::string > available_leaders_
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
static const std::string s_female
Standard string id (not translatable) for FEMALE.
Definition: race.hpp:29
static const std::string s_male
Standard string id (not translatable) for MALE.
Definition: race.hpp:30
@ FEMALE
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:1265
A single unit type that the player may recruit.
Definition: types.hpp:43
This class represents a single unit of a specific type.
Definition: unit.hpp:133
Definitions for the interface to Wesnoth Markup Language (WML).
#define ERR_MP
Definition: flg_manager.cpp:30
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1028
static std::string _(const char *str)
Definition: gettext.hpp:93
Standard logging facilities (interface).
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:70
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:100
std::vector< std::string > split(const config_attribute_value &val)
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1500
#define f