The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
flg_manager.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2013 - 2025
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"
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
28static 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
32namespace ng
33{
35 : faction_sort_order(sort_order::get_enum(cfg["auto_sort"].str()).value_or(sort_order::type::ascending))
36{
37}
38
39flg_manager::flg_manager(const era_metadata& era_info, const std::vector<const config*>& era_factions,
40 const config& side, const bool lock_settings, const bool use_map_settings, const bool saved_game)
41 : era_info_(era_info)
42 , era_factions_(era_factions)
43 , side_num_(side["side"].to_int())
44 , faction_from_recruit_(side["faction_from_recruit"].to_bool())
45 , original_faction_(get_default_faction(side)["faction"].str())
46 , original_recruit_(utils::split(get_default_faction(side)["recruit"].str()))
47 , saved_game_(saved_game)
48 , has_no_recruits_(original_recruit_.empty() && side["previous_recruits"].empty())
49 , faction_lock_(side["faction_lock"].to_bool(lock_settings))
50 , leader_lock_(side["leader_lock"].to_bool(lock_settings))
51 , available_factions_()
52 , available_leaders_()
53 , available_genders_()
54 , choosable_factions_()
55 , choosable_leaders_()
56 , choosable_genders_()
57 , current_faction_(nullptr)
58 , current_leader_("null")
59 , current_gender_("null")
60 , default_leader_type_("")
61 , default_leader_gender_("")
62{
63 std::string leader_id = side["id"];
64 bool found_leader;
65
66 leader_lock_ = leader_lock_ && (use_map_settings || lock_settings || default_leader_type_.empty());
67 faction_lock_ = faction_lock_ && (use_map_settings || lock_settings);
68
69 auto set_leader = [&](const config& cfg) {
70 found_leader = true;
71 leader_id = cfg["id"];
72 default_leader_type_ = cfg["type"];
73 default_leader_gender_ = cfg["gender"];
74 };
75
76 if(auto p_cfg = side.optional_child("leader")) {
77 set_leader(*p_cfg);
78 // If we are not the host of the game it can happen that the code overwrote
79 // the [leaders] type/gender and the original values are found in [default_faction]
80 // we still need the id from p_cfg tho.
81 if(auto p_ocfg = get_default_faction(side).optional_child("leader")) {
82 default_leader_type_ = (*p_ocfg)["type"];
83 default_leader_gender_ = (*p_ocfg)["gender"];
84 }
85 }
86
87 if(!leader_id.empty()) {
88 // Check if leader was carried over and now is in [unit] tag.
89 // (in this case we dont allow changing it, but we still want to show the corect unit type in the dialogs)
90 if(auto p_cfg = side.find_child("unit", "id", leader_id)) {
91 set_leader(*p_cfg);
92 leader_lock_ = true;
93 }
94 }
95
96 if(!found_leader) {
97 // Find a unit which can recruit.
98 if(auto p_cfg = side.find_child("unit", "canrecruit", "yes")) {
99 set_leader(*p_cfg);
100 leader_lock_ = true;
101 }
102 }
103
104
105 if(!default_leader_type_.empty() && default_leader_type_ != "random") {
106 if(unit_types.find(default_leader_type_) == nullptr) {
107 default_leader_type_.clear();
109 }
110 }
111
113
115
116}
117
118void flg_manager::set_current_faction(const unsigned index)
119{
120 assert(index < choosable_factions_.size());
122
125}
126
127void flg_manager::set_current_faction(const std::string& id)
128{
129 unsigned index = 0;
130 for(const config* faction : choosable_factions_) {
131 if((*faction)["id"] == id) {
132 set_current_faction(index);
133 return;
134 }
135 index++;
136 }
137
138 ERR_MP << "Faction '" << id << "' is not available for side " << side_num_ << " Ignoring";
139}
140
141void flg_manager::set_current_leader(const unsigned index)
142{
143 assert(index < choosable_leaders_.size());
145
148}
149
150void flg_manager::set_current_gender(const unsigned index)
151{
152 assert(index < choosable_genders_.size());
154}
155
157{
158 return (*current_faction_)["random_faction"].to_bool();
159}
160
161// When we use a random mode like "no mirror", "no ally mirror", the list of faction ids to avoid is passed
162// as an argument. It may be that for some scenario configuration, a strict no mirror assignment is not possible,
163// because there are too many sides, or some users have forced their faction choices to be matching, etc.
164// In that case we gracefully continue by ignoring the no mirror rule and assigning as we would have if it were off.
165// If there is still no options we throw a config error because it means the era is misconfigured.
166void flg_manager::resolve_random(randomness::mt_rng& rng, const std::vector<std::string>& avoid)
167{
168 if(is_random_faction()) {
169 std::vector<std::string> faction_choices, faction_excepts;
170
171 faction_choices = utils::split((*current_faction_)["choices"]);
172 if(faction_choices.size() == 1 && faction_choices.front().empty()) {
173 faction_choices.clear();
174 }
175
176 faction_excepts = utils::split((*current_faction_)["except"]);
177 if(faction_excepts.size() == 1 && faction_excepts.front().empty()) {
178 faction_excepts.clear();
179 }
180
181 // Builds the list of factions eligible for choice (non-random factions).
182 std::vector<int> nonrandom_sides;
183 std::vector<int> fallback_nonrandom_sides;
184 for(unsigned int i = 0; i < available_factions_.size(); ++i) {
185 const config& faction = *available_factions_[i];
186
187 if(faction["random_faction"].to_bool()) {
188 continue;
189 }
190
191 const std::string& faction_id = faction["id"];
192
193 if(!faction_choices.empty() && !utils::contains(faction_choices, faction_id)) {
194 continue;
195 }
196
197 if(!faction_excepts.empty() && utils::contains(faction_excepts, faction_id)) {
198 continue;
199 }
200
201 // This side is consistent with this random faction, remember as a fallback.
202 fallback_nonrandom_sides.push_back(i);
203
204 if(!avoid.empty() && utils::contains(avoid, faction_id)) {
205 continue;
206 }
207
208 // This side is consistent with this random faction, and the avoid factions argument.
209 nonrandom_sides.push_back(i);
210 }
211
212 if(nonrandom_sides.empty()) {
213 // There was no way to succeed consistently with the avoid factions argument, so ignore it as a fallback.
214 nonrandom_sides = fallback_nonrandom_sides;
215 }
216
217 if(nonrandom_sides.empty()) {
218 throw config::error(_("Only random sides in the current era."));
219 }
220
221 const int faction_index = nonrandom_sides[rng.get_next_random() % nonrandom_sides.size()];
223
226 }
227
228 if(current_leader_ == "random") {
229 std::vector<std::string> nonrandom_leaders = utils::split((*current_faction_)["random_leader"]);
230 if(nonrandom_leaders.empty()) {
231 for(const std::string& leader : available_leaders_) {
232 if(leader != "random") {
233 nonrandom_leaders.push_back(leader);
234 }
235 }
236 }
237
238 if(nonrandom_leaders.empty()) {
240 "Unable to find a leader type for faction $faction", {{"faction", (*current_faction_)["name"].str()}}));
241 } else {
242 const int lchoice = rng.get_next_random() % nonrandom_leaders.size();
243 current_leader_ = nonrandom_leaders[lchoice];
244
247 }
248 }
249
250 // Resolve random genders "very much" like standard unit code.
251 if(current_gender_ == "random") {
253 std::vector<std::string> nonrandom_genders;
254 for(const std::string& gender : available_genders_) {
255 if(gender != "random") {
256 nonrandom_genders.push_back(gender);
257 }
258 }
259
260 const int gchoice = rng.get_next_random() % nonrandom_genders.size();
261 current_gender_ = nonrandom_genders[gchoice];
262 } else {
263 throw config::error(VGETTEXT("Cannot obtain genders for invalid leader $leader", {{"leader", current_leader_}}));
264 }
265 }
266}
267
269{
270 const config* custom_faction = nullptr;
271 const bool show_custom_faction = original_faction_ == "Custom" || !has_no_recruits_ || faction_lock_;
272
273 for(const config* faction : era_factions_) {
274 if((*faction)["id"] == "Custom" && !show_custom_faction) {
275
276 // "Custom" faction should not be available if both
277 // "recruit" and "previous_recruits" lists are empty.
278 // However, it should be available if it was explicitly stated so.
279 custom_faction = faction;
280 continue;
281 }
282
283 // Add default faction to the top of the list.
284 if((*faction)["id"] == original_faction_) {
286 } else {
287 available_factions_.push_back(faction);
288 }
289 }
290
291 if(available_factions_.empty() && custom_faction) {
292 available_factions_.push_back(custom_faction);
293 }
294
295 assert(!available_factions_.empty());
296
298}
299
301{
302 available_leaders_.clear();
303
304 if(!default_leader_type_.empty() || !leader_lock_) {
305
306 int random_pos = 0;
307 // Add a default leader if there is one.
308 if(!default_leader_type_.empty()) {
310 random_pos = 1;
311 }
312
313 if(!saved_game_ && !is_random_faction()) {
314 if((*current_faction_)["id"] == "Custom") {
315 // Allow user to choose a leader from any faction.
316 for(const config* f : available_factions_) {
317 if((*f)["id"] != "Random") {
319 }
320 }
321 } else {
323 }
324
325 // Remove duplicate leaders.
326 std::set<std::string> seen;
327 utils::erase_if(available_leaders_, [&seen](const std::string& s) { return !seen.insert(s).second; });
328
329 if(available_leaders_.size() > 1) {
330 available_leaders_.insert(available_leaders_.begin() + random_pos, "random");
331 }
332 }
333 }
334
335 // If none of the possible leaders could be determined,
336 // use "null" as an indicator for empty leaders list.
337 if(available_leaders_.empty()) {
338 available_leaders_.push_back("null");
339 }
340
342}
343
345{
346 available_genders_.clear();
347
348 if(saved_game_) {
349 if(!default_leader_gender_.empty()) {
351 }
352 } else {
354 if(unit->genders().size() > 1 && !leader_lock_) {
355 available_genders_.push_back("random");
356 }
357
358 for(unit_race::GENDER gender : unit->genders()) {
359 const std::string gender_str = gender == unit_race::FEMALE
362
363 // Add default gender to the top of the list.
364 if(default_leader_gender_ == gender_str) {
365 available_genders_.insert(available_genders_.begin(), gender_str);
366 } else {
367 available_genders_.push_back(gender_str);
368 }
369 }
370 }
371 }
372
373 // If none of the possible genders could be determined,
374 // use "null" as an indicator for empty genders list.
375 if(available_genders_.empty()) {
376 available_genders_.push_back("null");
377 }
378
380}
381
383{
385
386 if(faction_lock_) {;
388 if(faction_index >= 0) {
389 const config* faction = choosable_factions_[faction_index];
391 choosable_factions_.push_back(faction);
392 }
393 }
394}
395
397{
399
400 if(!default_leader_type_.empty() && leader_lock_) {
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(utils::contains(available_genders_, default_gender)) {
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 = utils::ranges::find(choosable_factions_, default_faction,
434 [](const config* faction) { return (*faction)["id"]; });
435
436 if(default_faction_it != choosable_factions_.end()) {
437 set_current_faction(std::distance(choosable_factions_.begin(), default_faction_it));
438 } else {
440 }
441}
442
444{
445 std::vector<std::string> find;
446 std::string search_field;
447
448 if(!original_faction_.empty()) {
449 // Choose based on faction.
450 find.push_back(original_faction_);
451 search_field = "id";
452 } else if(faction_from_recruit_) {
453 // Choose based on recruit.
454 find = original_recruit_;
455 search_field = "recruit";
456 } else {
457 find.push_back("Custom");
458 search_field = "id";
459 }
460
461 int res = -1, index = 0, best_score = 0;
462 for(const config* faction : choosable_factions_) {
463 int faction_score = 0;
464 for(const std::string& search : find) {
465 for(const std::string& r : utils::split((*faction)[search_field])) {
466 if(r == search) {
467 ++faction_score;
468 break;
469 }
470 }
471 }
472
473 if(faction_score > best_score) {
474 best_score = faction_score;
475 res = index;
476 }
477
478 ++index;
479 }
480
481 return res;
482}
483
485{
486 assert(current_faction_);
487
489}
490
492{
493 std::vector<std::string> leaders_to_append = utils::split((*faction)["leader"]);
494
495 available_leaders_.insert(available_leaders_.end(), leaders_to_append.begin(),
496 leaders_to_append.end());
497}
498
499int flg_manager::faction_index(const config& faction) const
500{
501 const auto it = std::find(choosable_factions_.begin(), choosable_factions_.end(), &faction);
502
503 assert(it != choosable_factions_.end());
504 return std::distance(choosable_factions_.begin(), it);
505}
506
507int flg_manager::leader_index(const std::string& leader) const
508{
509 const auto it = std::find(choosable_leaders_.begin(), choosable_leaders_.end(), leader);
510
511 return it != choosable_leaders_.end() ? std::distance(choosable_leaders_.begin(), it) : -1;
512}
513
514int flg_manager::gender_index(const std::string& gender) const
515{
516 const auto it = std::find(choosable_genders_.begin(), choosable_genders_.end(), gender);
517
518 return it != choosable_genders_.end() ? std::distance(choosable_genders_.begin(), it) : -1;
519}
520
521void flg_manager::set_current_leader(const std::string& leader)
522{
523 int index = leader_index(leader);
524 if(index < 0) {
525 ERR_MP << "Leader '" << leader << "' is not available for side " << side_num_ << " Ignoring";
526 } else {
527 set_current_leader(index);
528 }
529}
530
531void flg_manager::set_current_gender(const std::string& gender)
532{
533 int index = gender_index(gender);
534 if(index < 0) {
535 ERR_MP << "Gender '" << gender << "' is not available for side " << side_num_ << " Ignoring";
536 } else {
537 set_current_gender(index);
538 }
539}
540
542{
543 if(auto df = cfg.optional_child("default_faction")) {
544 return *df;
545 } else {
546 return cfg;
547 }
548}
549
550} // end namespace ng
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void insert(std::string_view key, T &&value)
Inserts an attribute into the config.
Definition config.hpp:519
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition config.cpp:380
optional_config_impl< config > find_child(std::string_view 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:764
void clear()
Definition config.cpp:802
void update_available_factions()
const bool faction_from_recruit_
void update_choosable_leaders()
void update_choosable_factions()
flg_manager(const era_metadata &era_info, const std::vector< const config * > &era_factions, const config &side, bool lock_settings, bool use_map_settings, bool saved_game)
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_
void set_current_gender(const unsigned index)
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:62
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:1253
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:39
Definitions for the interface to Wesnoth Markup Language (WML).
#define ERR_MP
const config * cfg
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition gettext.hpp:100
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
auto find(Container &container, const Value &value, const Projection &projection={})
Definition general.hpp:213
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
std::vector< std::string > split(const config_attribute_value &val)
era_metadata(const config &cfg)
Parses an [era] tag.
The base template for associating string values with enum values.
Definition enum_base.hpp:33
static map_location::direction s
unit_type_data unit_types
Definition types.cpp:1499
#define i
#define f