The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
faction_select.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 - 2025
3 Part of the Battle for Wesnoth Project https://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
15#define GETTEXT_DOMAIN "wesnoth-lib"
16
18
19#include "formatter.hpp"
20#include "gettext.hpp"
23#include "gui/widgets/label.hpp"
28#include "help/help.hpp"
29#include "preferences/preferences.hpp" // for encountered_units
30#include "units/types.hpp"
31
32#ifdef __cpp_impl_three_way_comparison
33#include <compare>
34#endif
35#include <functional>
36
37namespace gui2::dialogs
38{
39namespace
40{
41/** Wrapper type to allow custom sorting for faction names. */
42struct faction_sorter
43{
44 const config* cfg;
45};
46
47#ifdef __cpp_impl_three_way_comparison
48
49/** Must be defined in the same namespace for ADL reasons. */
50auto operator<=>(const faction_sorter& lhs, const faction_sorter& rhs)
51{
52 // Since some eras have multiple random options we can't just
53 // assume there is only one random faction on top of the list.
54 bool lhs_rand = (*lhs.cfg)["random_faction"].to_bool();
55 bool rhs_rand = (*rhs.cfg)["random_faction"].to_bool();
56
57 // Group random factions together.
58 if(lhs_rand <=> rhs_rand != 0) {
59 return std::strong_ordering::greater;
60 }
61
62 std::string lhs_name = (*lhs.cfg)["name"];
63 std::string rhs_name = (*rhs.cfg)["name"];
64
65 // TODO C++20: define three-way comparison for t_string?
66 auto cmp = translation::compare(lhs_name, rhs_name);
67 if(cmp < 0) return std::strong_ordering::less;
68 if(cmp > 0) return std::strong_ordering::greater;
69
70 return std::strong_ordering::equal;
71}
72
73#else // TODO: remove block below as soon as humanly possible
74
75bool operator<(const faction_sorter& lhs, const faction_sorter& rhs)
76{
77 bool lhs_rand = (*lhs.cfg)["random_faction"].to_bool();
78 bool rhs_rand = (*rhs.cfg)["random_faction"].to_bool();
79
80 if(lhs_rand && !rhs_rand) return true;
81 if(!lhs_rand && rhs_rand) return false;
82
83 return translation::compare((*lhs.cfg)["name"].str(), (*rhs.cfg)["name"].str()) < 0;
84}
85
86bool operator>(const faction_sorter& lhs, const faction_sorter& rhs)
87{
88 bool lhs_rand = (*lhs.cfg)["random_faction"].to_bool();
89 bool rhs_rand = (*rhs.cfg)["random_faction"].to_bool();
90
91 if(lhs_rand && !rhs_rand) return false;
92 if(!lhs_rand && rhs_rand) return true;
93
94 return translation::compare((*lhs.cfg)["name"].str(), (*rhs.cfg)["name"].str()) > 0;
95}
96
97#endif
98
99} // namespace
100
101REGISTER_DIALOG(faction_select)
102
103faction_select::faction_select(ng::flg_manager& flg_manager, const std::string& color, const int side)
104 : modal_dialog(window_id())
105 , flg_manager_(flg_manager)
106 , tc_color_(color)
107 , side_(side)
108 , last_faction_(flg_manager.current_faction_index())
109 , last_leader_(flg_manager.current_leader_index())
110 , last_gender_(flg_manager.current_gender_index())
111{
112}
113
115{
116 find_widget<label>("starting_pos").set_label(std::to_string(side_));
117
118 //
119 // Set up gender radio buttons
120 //
121 toggle_button& gender_rand = find_widget<toggle_button>("gender_random");
122 toggle_button& gender_male = find_widget<toggle_button>("gender_male");
123 toggle_button& gender_female = find_widget<toggle_button>("gender_female");
124
125 gender_toggle_.add_member(&gender_rand, "random");
128
130
132 std::bind(&faction_select::on_gender_select, this, std::placeholders::_2));
133
134 //
135 // Set up leader menu button
136 //
137 connect_signal_notify_modified(find_widget<menu_button>("leader_menu"),
138 std::bind(&faction_select::on_leader_select, this));
139
140 // Leader's profile button
141 find_widget<button>("type_profile").connect_click_handler(
143
144 //
145 // Set up faction list
146 //
147 listbox& list = find_widget<listbox>("faction_list");
148
149 keyboard_capture(&list);
150
151 connect_signal_notify_modified(list,
152 std::bind(&faction_select::on_faction_select, this));
153
154 for(const config *s : flg_manager_.choosable_factions()) {
155 const config& side = *s;
156
157 // flag_rgb here is unrelated to any handling in the unit class
158 const std::string flag_rgb = !side["flag_rgb"].empty() ? side["flag_rgb"].str() : "magenta";
159
160 list.add_row(widget_data{
161 { "faction_image", {
162 { "label", (formatter() << side["image"] << "~RC(" << flag_rgb << ">" << tc_color_ << ")").str() }
163 }},
164 { "faction_name", {
165 { "label", side["name"].str() }
166 }},
167 });
168 }
169
171 list.set_sorters([this](std::size_t i) { return faction_sorter{flg_manager_.choosable_factions()[i]}; });
173
175}
176
178{
179 const int selected_row = find_widget<listbox>("faction_list").get_selected_row();
180
181 if(selected_row == -1) {
182 return;
183 }
184
185 // Since set_current_faction overrides the current leader, save a copy of the previous leader index so the
186 // leader dropdown can be set to the appropriate initial selection.
187 const int previous_leader_selection = flg_manager_.current_leader_index();
188
189 flg_manager_.set_current_faction(selected_row);
190
191 std::vector<config> leaders;
192
193 for(const std::string& leader : flg_manager_.choosable_leaders()) {
194 const unit_type* unit = unit_types.find(leader);
195
196 if(unit) {
197 const std::string icon = formatter() << unit->image() << "~RC(" << unit->flag_rgb() << ">" << tc_color_ << ")";
198 leaders.emplace_back("label", unit->type_name(), "icon", icon);
199 } else if(leader == "random") {
200 leaders.emplace_back("label", _("Random"), "icon", ng::random_enemy_picture);
201 } else if(leader == "null") {
202 leaders.emplace_back("label", font::unicode_em_dash);
203 } else {
204 leaders.emplace_back("label", "?");
205 }
206 }
207
208 menu_button& leader_dropdown = find_widget<menu_button>("leader_menu");
209
210 leader_dropdown.set_values(leaders, std::min<int>(leaders.size() - 1, previous_leader_selection));
211 leader_dropdown.set_active(leaders.size() > 1 && !flg_manager_.is_saved_game());
212
214
215 // Print recruits
216 std::vector<t_string> recruit_names;
217 for(const auto& recruit : utils::split(flg_manager_.current_faction()["recruit"])) {
218 if(const unit_type* rt = unit_types.find(recruit)) {
219 recruit_names.push_back(rt->type_name());
220 }
221 }
222
223 std::sort(recruit_names.begin(), recruit_names.end(), [](const std::string& s1, const std::string& s2) {
224 return translation::compare(s1, s2) < 0;
225 });
226
227 find_widget<styled_widget>("recruits").set_label(utils::bullet_list(recruit_names, 0));
228}
229
231{
232 flg_manager_.set_current_leader(find_widget<menu_button>("leader_menu").get_value());
233
234 // TODO: should we decouple this from the flg manager and instead just check the unit type directly?
235 // If that's done so, we'd need to come up with a different check for Random availability.
236 gender_toggle_.set_members_enabled([this](const std::string& gender) {
238 });
239
241
242 // Disable the profile button if leader_type is dash or "Random"
243 button& profile_button = find_widget<button>("type_profile");
244 profile_button.set_active(unit_types.find(flg_manager_.current_leader()) != nullptr);
245}
246
254
255void faction_select::on_gender_select(const std::string& val)
256{
258
260}
261
263{
264 std::string leader_image = ng::random_enemy_picture;
265
268 leader_image = formatter() << utg.image() << "~RC(" << utg.flag_rgb() << ">" << tc_color_ << ")";
269 }
270
271 find_widget<drawing>("leader_image").set_label(leader_image);
272}
273
275{
276 //
277 // If we're canceling, restore the previous selections. It might be worth looking
278 // into only making selections at all here in post_show, but that would require a
279 // refactor of the flg_manager class.
280 //
281 // Also, note it's important to set these in the order of faction -> leader -> gender
282 // or the saved indices will be invalid!
283 //
284 // -- vultraz, 2018-06-16
285 //
286 if(get_retval() != retval::OK) {
290 }
291}
292
293} // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
bool empty() const
Definition config.cpp:823
std::ostringstream wrapper.
Definition formatter.hpp:40
Simple push button.
Definition button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition button.cpp:64
virtual void post_show() override
Actions to be taken after the window has been shown.
virtual void pre_show() override
Actions to be taken before showing the window.
group< std::string > gender_toggle_
void on_gender_select(const std::string &val)
Abstract base class for all modal dialogs.
void on_modified(const Func &func)
Sets a common callback function for all members.
Definition group.hpp:121
void add_member(selectable_item *w, const T &value)
Adds a widget/value pair to the group map.
Definition group.hpp:41
void set_members_enabled(const Func &predicate)
Wrapper for enabling or disabling member widgets.
Definition group.hpp:151
void set_member_states(const T &value)
Sets the toggle values for all widgets besides the one associated with the specified value to false.
Definition group.hpp:110
The listbox class.
Definition listbox.hpp:41
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition listbox.cpp:92
void set_active_sorter(std::string_view id, sort_order::type order, bool select_first=false)
Sorts the listbox by a pre-set sorting option.
Definition listbox.cpp:599
void set_sorters(Args &&... functors)
Registers sorting controls using magic index IDs.
Definition listbox.hpp:306
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition listbox.cpp:267
void set_values(const std::vector<::config > &values, unsigned selected=0)
virtual void set_active(const bool active) override
See styled_widget::set_active.
virtual void set_label(const t_string &text)
void keyboard_capture(widget *widget)
Definition window.cpp:1197
int get_retval()
Definition window.hpp:394
const era_metadata & era_info() const
const std::vector< const config * > & choosable_factions() const
const std::vector< std::string > & choosable_leaders() const
void set_current_faction(const unsigned index)
const std::string & current_gender() const
const std::string & current_leader() const
bool is_saved_game() const
int current_leader_index() const
const config & current_faction() const
void set_current_leader(const unsigned index)
const std::vector< std::string > & choosable_genders() const
int current_faction_index() const
void set_current_gender(const unsigned index)
static prefs & get()
std::set< std::string > & encountered_units()
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
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
const std::string & image() const
Definition types.hpp:180
const unit_type & get_gender_unit_type(const std::string &gender) const
Returns a gendered variant of this unit_type.
Definition types.cpp:416
const std::string & flag_rgb() const
Definition types.cpp:676
This class represents a single unit of a specific type.
Definition unit.hpp:39
const config * cfg
static bool operator<(const placing_info &a, const placing_info &b)
bool operator>(const version_info &l, const version_info &r)
Greater-than operator for version_info.
static std::string _(const char *str)
Definition gettext.hpp:100
const t_string & type_name() const
Gets the translatable name of this unit's type.
Definition unit.hpp:275
const std::string & flag_rgb() const
Get the source color palette to use when recoloring the unit's image.
Definition unit.cpp:1196
This file contains the window object, this object is a top level container which has the event manage...
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
const std::string unicode_em_dash
Definition constants.cpp:44
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
void show_unit_description(const unit &u)
Definition help.cpp:64
const std::string random_enemy_picture("units/random-dice.png")
STL namespace.
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
Definition gettext.cpp:502
std::string bullet_list(const T &v, std::size_t indent=4, const std::string &bullet=font::unicode_bullet)
Generates a new string containing a bullet list.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
std::vector< std::string > split(const config_attribute_value &val)
sort_order::type faction_sort_order
The order to display factions when a player selects their leader.
static map_location::direction s
unit_type_data unit_types
Definition types.cpp:1499
#define i