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