The Battle for Wesnoth  1.19.8+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_faction_(get_default_faction(side)["faction"].str())
41  , original_recruit_(utils::split(get_default_faction(side)["recruit"].str()))
42  , saved_game_(saved_game)
43  , has_no_recruits_(original_recruit_.empty() && side["previous_recruits"].empty())
44  , faction_lock_(side["faction_lock"].to_bool(lock_settings))
45  , leader_lock_(side["leader_lock"].to_bool(lock_settings))
46  , available_factions_()
47  , available_leaders_()
48  , available_genders_()
49  , choosable_factions_()
50  , choosable_leaders_()
51  , choosable_genders_()
52  , current_faction_(nullptr)
53  , current_leader_("null")
54  , current_gender_("null")
55  , default_leader_type_("")
56  , default_leader_gender_("")
57 {
58  std::string leader_id = side["id"];
59  bool found_leader;
60 
61  leader_lock_ = leader_lock_ && (use_map_settings || lock_settings || default_leader_type_.empty());
62  faction_lock_ = faction_lock_ && (use_map_settings || lock_settings);
63 
64  auto set_leader = [&](const config& cfg) {
65  found_leader = true;
66  leader_id = cfg["id"];
67  default_leader_type_ = cfg["type"];
68  default_leader_gender_ = cfg["gender"];
69  };
70 
71  if(auto p_cfg = side.optional_child("leader")) {
72  set_leader(*p_cfg);
73  // If we are not the host of the game it can happen that the code overwrote
74  // the [leaders] type/gender and the original values are found in [default_faction]
75  // we still need the id from p_cfg tho.
76  if(auto p_ocfg = get_default_faction(side).optional_child("leader")) {
77  default_leader_type_ = (*p_ocfg)["type"];
78  default_leader_gender_ = (*p_ocfg)["gender"];
79  }
80  }
81 
82  if(!leader_id.empty()) {
83  // Check if leader was carried over and now is in [unit] tag.
84  // (in this case we dont allow changing it, but we still want to show the corect unit type in the dialogs)
85  if(auto p_cfg = side.find_child("unit", "id", leader_id)) {
86  set_leader(*p_cfg);
87  leader_lock_ = true;
88  }
89  }
90 
91  if(!found_leader) {
92  // Find a unit which can recruit.
93  if(auto p_cfg = side.find_child("unit", "canrecruit", "yes")) {
94  set_leader(*p_cfg);
95  leader_lock_ = true;
96  }
97  }
98 
99 
100  if(!default_leader_type_.empty() && default_leader_type_ != "random") {
101  if(unit_types.find(default_leader_type_) == nullptr) {
102  default_leader_type_.clear();
103  default_leader_gender_.clear();
104  }
105  }
106 
108 
110 
111 }
112 
114 {
115  assert(index < choosable_factions_.size());
117 
120 }
121 
122 void flg_manager::set_current_faction(const std::string& id)
123 {
124  unsigned index = 0;
125  for(const config* faction : choosable_factions_) {
126  if((*faction)["id"] == id) {
128  return;
129  }
130  index++;
131  }
132 
133  ERR_MP << "Faction '" << id << "' is not available for side " << side_num_ << " Ignoring";
134 }
135 
137 {
138  assert(index < choosable_leaders_.size());
140 
143 }
144 
146 {
147  assert(index < choosable_genders_.size());
149 }
150 
152 {
153  return (*current_faction_)["random_faction"].to_bool();
154 }
155 
156 // When we use a random mode like "no mirror", "no ally mirror", the list of faction ids to avoid is passed
157 // as an argument. It may be that for some scenario configuration, a strict no mirror assignment is not possible,
158 // because there are too many sides, or some users have forced their faction choices to be matching, etc.
159 // In that case we gracefully continue by ignoring the no mirror rule and assigning as we would have if it were off.
160 // If there is still no options we throw a config error because it means the era is misconfigured.
161 void flg_manager::resolve_random(randomness::mt_rng& rng, const std::vector<std::string>& avoid)
162 {
163  if(is_random_faction()) {
164  std::vector<std::string> faction_choices, faction_excepts;
165 
166  faction_choices = utils::split((*current_faction_)["choices"]);
167  if(faction_choices.size() == 1 && faction_choices.front().empty()) {
168  faction_choices.clear();
169  }
170 
171  faction_excepts = utils::split((*current_faction_)["except"]);
172  if(faction_excepts.size() == 1 && faction_excepts.front().empty()) {
173  faction_excepts.clear();
174  }
175 
176  // Builds the list of factions eligible for choice (non-random factions).
177  std::vector<int> nonrandom_sides;
178  std::vector<int> fallback_nonrandom_sides;
179  for(unsigned int i = 0; i < available_factions_.size(); ++i) {
180  const config& faction = *available_factions_[i];
181 
182  if(faction["random_faction"].to_bool()) {
183  continue;
184  }
185 
186  const std::string& faction_id = faction["id"];
187 
188  if(!faction_choices.empty() && std::find(faction_choices.begin(), faction_choices.end(),
189  faction_id) == faction_choices.end()) {
190  continue;
191  }
192 
193  if(!faction_excepts.empty() && std::find(faction_excepts.begin(), faction_excepts.end(),
194  faction_id) != faction_excepts.end()) {
195  continue;
196  }
197 
198  // This side is consistent with this random faction, remember as a fallback.
199  fallback_nonrandom_sides.push_back(i);
200 
201  if(!avoid.empty() && std::find(avoid.begin(), avoid.end(),
202  faction_id) != avoid.end()) {
203  continue;
204  }
205 
206  // This side is consistent with this random faction, and the avoid factions argument.
207  nonrandom_sides.push_back(i);
208  }
209 
210  if(nonrandom_sides.empty()) {
211  // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
212  nonrandom_sides = fallback_nonrandom_sides;
213  }
214 
215  if(nonrandom_sides.empty()) {
216  throw config::error(_("Only random sides in the current era."));
217  }
218 
219  const int faction_index = nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
221 
224  }
225 
226  if(current_leader_ == "random") {
227  std::vector<std::string> nonrandom_leaders = utils::split((*current_faction_)["random_leader"]);
228  if(nonrandom_leaders.empty()) {
229  for(const std::string& leader : available_leaders_) {
230  if(leader != "random") {
231  nonrandom_leaders.push_back(leader);
232  }
233  }
234  }
235 
236  if(nonrandom_leaders.empty()) {
237  throw config::error(VGETTEXT(
238  "Unable to find a leader type for faction $faction", {{"faction", (*current_faction_)["name"].str()}}));
239  } else {
240  const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
241  current_leader_ = nonrandom_leaders[lchoice];
242 
245  }
246  }
247 
248  // Resolve random genders "very much" like standard unit code.
249  if(current_gender_ == "random") {
251  std::vector<std::string> nonrandom_genders;
252  for(const std::string& gender : available_genders_) {
253  if(gender != "random") {
254  nonrandom_genders.push_back(gender);
255  }
256  }
257 
258  const int gchoice = rng.get_next_random() % nonrandom_genders.size();
259  current_gender_ = nonrandom_genders[gchoice];
260  } else {
261  throw config::error(VGETTEXT("Cannot obtain genders for invalid leader $leader", {{"leader", current_leader_}}));
262  }
263  }
264 }
265 
267 {
268  const config* custom_faction = nullptr;
269  const bool show_custom_faction = original_faction_ == "Custom" || !has_no_recruits_ || faction_lock_;
270 
271  for(const config* faction : era_factions_) {
272  if((*faction)["id"] == "Custom" && !show_custom_faction) {
273 
274  // "Custom" faction should not be available if both
275  // "recruit" and "previous_recruits" lists are empty.
276  // However, it should be available if it was explicitly stated so.
277  custom_faction = faction;
278  continue;
279  }
280 
281  // Add default faction to the top of the list.
282  if(original_faction_ == (*faction)["id"]) {
283  available_factions_.insert(available_factions_.begin(), faction);
284  } else {
285  available_factions_.push_back(faction);
286  }
287  }
288 
289  if(available_factions_.empty() && custom_faction) {
290  available_factions_.push_back(custom_faction);
291  }
292 
293  assert(!available_factions_.empty());
294 
296 }
297 
299 {
300  available_leaders_.clear();
301 
302  if(!default_leader_type_.empty() || !leader_lock_) {
303 
304  int random_pos = 0;
305  // Add a default leader if there is one.
306  if(!default_leader_type_.empty()) {
308  random_pos = 1;
309  }
310 
311  if(!saved_game_ && !is_random_faction()) {
312  if((*current_faction_)["id"] == "Custom") {
313  // Allow user to choose a leader from any faction.
314  for(const config* f : available_factions_) {
315  if((*f)["id"] != "Random") {
317  }
318  }
319  } else {
321  }
322 
323  // Remove duplicate leaders.
324  std::set<std::string> seen;
325  utils::erase_if(available_leaders_, [&seen](const std::string& s) { return !seen.insert(s).second; });
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(!default_leader_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_) {
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.
457  search_field = "recruit";
458  } else {
459  find.push_back("Custom");
460  search_field = "id";
461  }
462 
463  int res = -1, index = 0, best_score = 0;
464  for(const config* faction : choosable_factions_) {
465  int faction_score = 0;
466  for(const std::string& search : find) {
467  for(const std::string& r : utils::split((*faction)[search_field])) {
468  if(r == search) {
469  ++faction_score;
470  break;
471  }
472  }
473  }
474 
475  if(faction_score > best_score) {
476  best_score = faction_score;
477  res = index;
478  }
479 
480  ++index;
481  }
482 
483  return res;
484 }
485 
487 {
488  assert(current_faction_);
489 
491 }
492 
494 {
495  std::vector<std::string> leaders_to_append = utils::split((*faction)["leader"]);
496 
497  available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
498  leaders_to_append.end());
499 }
500 
501 int flg_manager::faction_index(const config& faction) const
502 {
503  const auto it = std::find(choosable_factions_.begin(), choosable_factions_.end(), &faction);
504 
505  assert(it != choosable_factions_.end());
506  return std::distance(choosable_factions_.begin(), it);
507 }
508 
509 int flg_manager::leader_index(const std::string& leader) const
510 {
511  const auto it = std::find(choosable_leaders_.begin(), choosable_leaders_.end(), leader);
512 
513  return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
514 }
515 
516 int flg_manager::gender_index(const std::string& gender) const
517 {
518  const auto it = std::find(choosable_genders_.begin(), choosable_genders_.end(), gender);
519 
520  return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
521 }
522 
523 void flg_manager::set_current_leader(const std::string& leader)
524 {
525  int index = leader_index(leader);
526  if(index < 0) {
527  ERR_MP << "Leader '" << leader << "' is not available for side " << side_num_ << " Ignoring";
528  } else {
530  }
531 }
532 
533 void flg_manager::set_current_gender(const std::string& gender)
534 {
535  int index = gender_index(gender);
536  if(index < 0) {
537  ERR_MP << "Gender '" << gender << "' is not available for side " << side_num_ << " Ignoring";
538  } else {
540  }
541 }
542 
544 {
545  if(auto df = cfg.optional_child("default_faction")) {
546  return *df;
547  } else {
548  return cfg;
549  }
550 }
551 
552 } // end namespace ng
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
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:780
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:380
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 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()
void append_leaders_from_faction(const config *faction)
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:1029
static std::string _(const char *str)
Definition: gettext.hpp:93
Standard logging facilities (interface).
std::size_t index(std::string_view 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:106
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1504
#define f