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