The Battle for Wesnoth  1.19.25+dev
preferences.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2024 - 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 /**
16  * @file
17  * Get and set user-preferences.
18  */
19 
20 #define GETTEXT_DOMAIN "wesnoth-lib"
21 
23 
24 #include "cursor.hpp"
25 #include "game_board.hpp"
26 #include "game_display.hpp"
27 #include "formula/string_utils.hpp"
28 #include "game_config.hpp"
29 #include "game_data.hpp"
30 #include "gettext.hpp"
34 #include "hotkey/hotkey_item.hpp"
35 #include "log.hpp"
36 #include "map_settings.hpp"
37 #include "map/map.hpp"
38 #include "resources.hpp"
39 #include "serialization/chrono.hpp"
40 #include "serialization/parser.hpp"
41 #include "sound.hpp"
42 #include "units/unit.hpp"
43 #include "video.hpp"
44 
45 #include <sys/stat.h> // for setting the permissions of the preferences file
46 #include <boost/algorithm/string.hpp>
47 
48 #ifdef _WIN32
50 #include <windows.h>
51 #endif
52 
53 #ifndef __APPLE__
54 #include <openssl/evp.h>
55 #include <openssl/err.h>
56 #else
57 #include <CommonCrypto/CommonCryptor.h>
58 #endif
59 
60 static lg::log_domain log_config("config");
61 #define ERR_CFG LOG_STREAM(err , log_config)
62 #define DBG_CFG LOG_STREAM(debug , log_config)
63 
64 static lg::log_domain log_filesystem("filesystem");
65 #define ERR_FS LOG_STREAM(err, log_filesystem)
66 
67 static lg::log_domain advanced_preferences("advanced_preferences");
68 #define ERR_ADV LOG_STREAM(err, advanced_preferences)
69 
70 using namespace std::chrono_literals;
71 
73  : preferences_()
74  , fps_(false)
75  , completed_campaigns_()
76  , encountered_units_set_()
77  , encountered_terrains_set_()
78  , history_map_()
79  , acquaintances_()
80  , option_values_()
81  , options_initialized_(false)
82  , mp_modifications_()
83  , mp_modifications_initialized_(false)
84  , sp_modifications_()
85  , sp_modifications_initialized_(false)
86  , message_private_on_(false)
87  , credentials_()
88  , advanced_prefs_()
89 {
92 
93  // make sure this has a default set
94  if(!preferences_.has_attribute("scroll_threshold")) {
95  preferences_[prefs_list::scroll_threshold] = 10;
96  }
97 
98  for(const config& acfg : preferences_.child_range(prefs_list::acquaintance)) {
100  acquaintances_[ac.get_nick()] = ac;
101  }
102 }
103 
105 {
106  config campaigns;
107  for(const auto& elem : completed_campaigns_) {
108  config cmp;
109  cmp["name"] = elem.first;
110  cmp["difficulty_levels"] = utils::join(elem.second);
111  campaigns.add_child("campaign", cmp);
112  }
113 
114  set_child(prefs_list::completed_campaigns, campaigns);
115 
116  preferences_[prefs_list::encountered_units] = utils::join(encountered_units_set_);
118  preferences_[prefs_list::encountered_terrain_list] = t_translation::write_list(terrain);
119 
120  /* Structure of the history
121  [history]
122  [history_id]
123  [line]
124  message = foobar
125  [/line]
126  */
127  config history;
128  for(const auto& history_id : history_map_) {
129  config history_id_cfg; // [history_id]
130  for(const std::string& line : history_id.second) {
131  config cfg; // [line]
132 
133  cfg["message"] = line;
134  history_id_cfg.add_child("line", std::move(cfg));
135  }
136 
137  history.add_child(history_id.first, history_id_cfg);
138  }
139  set_child(prefs_list::history, history);
140 
141  preferences_.clear_children(prefs_list::acquaintance);
142 
143  for(auto& a : acquaintances_) {
144  config& item = preferences_.add_child(prefs_list::acquaintance);
145  a.second.save(item);
146  }
147 
148  history_map_.clear();
149  encountered_units_set_.clear();
151 
152  try {
153  if(!no_preferences_save) {
155  }
156  } catch (...) {
157  ERR_FS << "Failed to write preferences due to exception: " << utils::get_unknown_exception_type();
158  }
159 }
160 
162 {
163  advanced_prefs_.clear();
164 
165  for(const config& pref : gc.child_range("advanced_preference")) {
166  try {
167  advanced_prefs_.emplace_back(pref);
168  } catch(const std::invalid_argument& e) {
169  ERR_ADV << e.what();
170  continue;
171  }
172  }
173 
174  // show_deprecation has a different default on the dev branch
175  if(game_config::wesnoth_version.is_dev_version()) {
177  if(op.field == prefs_list::show_deprecation) {
178  op.cfg["default"] = true;
179  }
180  }
181  }
182 
183  std::sort(advanced_prefs_.begin(), advanced_prefs_.end(), [](const auto& lhs, const auto& rhs) { return translation::icompare(lhs.name, rhs.name) < 0; });
184 }
185 
186 void prefs::migrate_preferences(const std::string& migrate_prefs_file)
187 {
188  if(migrate_prefs_file != filesystem::get_synced_prefs_file() && filesystem::file_exists(migrate_prefs_file)) {
189  // if the file doesn't exist, just copy the file over
190  // else need to merge the preferences file
193  } else {
194  auto current_stream = filesystem::istream_file(filesystem::get_synced_prefs_file(), false);
195  config current_cfg = io::read(*current_stream);
196 
197  auto old_stream = filesystem::istream_file(migrate_prefs_file, false);
198  config old_cfg = io::read(*old_stream);
199 
200  // when both files have the same attribute, use the one from whichever was most recently modified
201  bool current_prefs_are_older = filesystem::file_modified_time(filesystem::get_synced_prefs_file()) < filesystem::file_modified_time(migrate_prefs_file);
202  for(const auto& [key, value] : old_cfg.attribute_range()) {
203  if(current_prefs_are_older || !current_cfg.has_attribute(key)) {
204  preferences_[key] = value;
205  }
206  }
207 
208  // don't touch child tags
209 
211  }
212  }
213 }
214 
215 // TODO: remove after 1.20. Keeps pre-1.20 profiles (which lack the pref) on "Default RNG" to keep old players happy.
217 {
218  if(!preferences_.has_attribute(prefs_list::campaign_rng_mode)) {
219  preferences_[prefs_list::campaign_rng_mode] = "default";
221  }
222 }
223 
225 {
229 }
230 
231 std::set<std::string> prefs::all_attributes()
232 {
233  std::set<std::string> attrs;
234 
235  // attributes that exist in the preferences file
236  for(const auto& attr : preferences_.attribute_range()) {
237  attrs.emplace(attr.first);
238  }
239  // all mainline preference attributes, whether they're set or not
240  for(const auto attr : prefs_list::values) {
241  attrs.emplace(attr);
242  }
243 
244  return attrs;
245 }
246 
248 {
250  try{
251  config default_prefs;
252  config unsynced_prefs;
253  config synced_prefs;
254 #ifdef DEFAULT_PREFS_PATH
255  // NOTE: the system preferences file is only ever relevant for the first time wesnoth starts
256  // any default values will subsequently be written to the normal preferences files, which takes precedence over any values in the system preferences file
257  {
259  default_prefs = io::read(*stream);
260  }
261 #endif
262  {
264  unsynced_prefs = io::read(*stream);
265  }
266 
267  {
269  synced_prefs = io::read(*stream);
270  }
271 
272  preferences_.merge_with(default_prefs);
273  preferences_.merge_with(unsynced_prefs);
274  preferences_.merge_with(synced_prefs);
275 
276  // check for any unknown preferences
277  for(const auto& [key, _] : synced_prefs.attribute_range()) {
279  unknown_synced_attributes_.insert(key);
280  }
281  }
282  for(const auto& [key, _] : unsynced_prefs.attribute_range()) {
284  unknown_unsynced_attributes_.insert(key);
285  }
286  }
287 
288  for(const auto [key, _] : synced_prefs.all_children_view()) {
289  if(!utils::contains(synced_children_, key)) {
290  unknown_synced_children_.insert(key);
291  }
292  }
293  for(const auto [key, _] : unsynced_prefs.all_children_view()) {
295  unknown_unsynced_children_.insert(key);
296  }
297  }
298  } catch(const config::error& e) {
299  ERR_CFG << "Error loading preference, message: " << e.what();
300  }
301 
304 
305  /*
306  completed_campaigns = "A,B,C"
307  [completed_campaigns]
308  [campaign]
309  name = "A"
310  difficulty_levels = "EASY,MEDIUM"
311  [/campaign]
312  [/completed_campaigns]
313  */
314  // presumably for backwards compatibility?
315  // nothing actually sets the attribute, only the child tags
316  for(const std::string& c : utils::split(preferences_[prefs_list::completed_campaigns])) {
317  completed_campaigns_[c]; // create the elements
318  }
319 
320  if(auto ccc = get_child(prefs_list::completed_campaigns)) {
321  for(const config& cc : ccc->child_range("campaign")) {
322  std::set<std::string>& d = completed_campaigns_[cc["name"]];
323  std::vector<std::string> nd = utils::split(cc["difficulty_levels"]);
324  std::copy(nd.begin(), nd.end(), std::inserter(d, d.begin()));
325  }
326  }
327 
328  encountered_units_set_ = utils::split_set(preferences_[prefs_list::encountered_units].str());
329 
330  const t_translation::ter_list terrain(t_translation::read_list(preferences_[prefs_list::encountered_terrain_list].str()));
331  encountered_terrains_set_.insert(terrain.begin(), terrain.end());
332 
333  if(auto history = get_child(prefs_list::history)) {
334  /* Structure of the history
335  [history]
336  [history_id]
337  [line]
338  message = foobar
339  [/line]
340  */
341  for(const auto [key, cfg] : history->all_children_view()) {
342  for(const config& l : cfg.child_range("line")) {
343  history_map_[key].push_back(l["message"]);
344  }
345  }
346  }
347 }
348 
350 {
351 #ifndef _WIN32
352  bool synced_prefs_file_existed = filesystem::file_exists(filesystem::get_synced_prefs_file());
353  bool unsynced_prefs_file_existed = filesystem::file_exists(filesystem::get_unsynced_prefs_file());
354 #endif
355 
356  config synced;
357  config unsynced;
358 
359  for(const char* attr : synced_attributes_) {
360  if(preferences_.has_attribute(attr)) {
361  synced[attr] = preferences_[attr];
362  }
363  }
364  for(const char* attr : synced_children_) {
365  for(const auto& child : preferences_.child_range(attr)) {
366  synced.add_child(attr, child);
367  }
368  }
369 
370  for(const char* attr : unsynced_attributes_) {
371  if(preferences_.has_attribute(attr)) {
372  unsynced[attr] = preferences_[attr];
373  }
374  }
375  for(const char* attr : unsynced_children_) {
376  for(const auto& child : preferences_.child_range(attr)) {
377  unsynced.add_child(attr, child);
378  }
379  }
380 
381  // write any unknown preferences back out
382  for(const std::string& attr : unknown_synced_attributes_) {
383  synced[attr] = preferences_[attr];
384  }
385  for(const std::string& attr : unknown_synced_children_) {
386  for(const auto& child : preferences_.child_range(attr)) {
387  synced.add_child(attr, child);
388  }
389  }
390 
391  for(const std::string& attr : unknown_unsynced_attributes_) {
392  unsynced[attr] = preferences_[attr];
393  }
394  for(const std::string& attr : unknown_unsynced_children_) {
395  for(const auto& child : preferences_.child_range(attr)) {
396  unsynced.add_child(attr, child);
397  }
398  }
399 
400  try {
402  } catch(const filesystem::io_exception&) {
403  ERR_FS << "error writing to synced preferences file '" << filesystem::get_synced_prefs_file() << "'";
404  }
405 
406  try {
408  } catch(const filesystem::io_exception&) {
409  ERR_FS << "error writing to unsynced preferences file '" << filesystem::get_unsynced_prefs_file() << "'";
410  }
411 
413 
414 #ifndef _WIN32
415  if(!synced_prefs_file_existed) {
416  if(chmod(filesystem::get_synced_prefs_file().c_str(), 0600) == -1) {
417  ERR_FS << "error setting permissions of preferences file '" << filesystem::get_synced_prefs_file() << "'";
418  }
419  }
420  if(!unsynced_prefs_file_existed) {
421  if(chmod(filesystem::get_unsynced_prefs_file().c_str(), 0600) == -1) {
422  ERR_FS << "error setting permissions of unsynced preferences file '" << filesystem::get_unsynced_prefs_file() << "'";
423  }
424  }
425 #endif
426 }
427 
429 {
430  // Zero them before clearing.
431  // Probably overly paranoid, but doesn't hurt?
432  for(auto& cred : credentials_) {
433  std::fill(cred.username.begin(), cred.username.end(), '\0');
434  std::fill(cred.server.begin(), cred.server.end(), '\0');
435  }
436  credentials_.clear();
437 }
438 
440 {
441  if(!remember_password()) {
442  return;
443  }
445  std::string cred_file = filesystem::get_credentials_file();
446  if(!filesystem::file_exists(cred_file)) {
447  return;
448  }
449  filesystem::scoped_istream stream = filesystem::istream_file(cred_file, false);
450  // Credentials file is a binary blob, so use streambuf iterator
451  preferences::secure_buffer data((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
453  if(data.empty() || data[0] != pref_constants::CREDENTIAL_SEPARATOR) {
454  ERR_CFG << "Invalid data in credentials file";
455  return;
456  }
457  for(const std::string& elem : utils::split(std::string(data.begin(), data.end()), pref_constants::CREDENTIAL_SEPARATOR, utils::REMOVE_EMPTY)) {
458  std::size_t at = elem.find_last_of('@');
459  std::size_t eq = elem.find_first_of('=', at + 1);
460  if(at != std::string::npos && eq != std::string::npos) {
461  preferences::secure_buffer key(elem.begin() + eq + 1, elem.end());
462  credentials_.emplace_back(elem.substr(0, at), elem.substr(at + 1, eq - at - 1), unescape(key));
463  }
464  }
465 }
466 
468 {
469  if(!remember_password()) {
471  return;
472  }
473 
474 #ifndef _WIN32
475  bool creds_file_existed = filesystem::file_exists(filesystem::get_credentials_file());
476 #endif
477 
478  preferences::secure_buffer credentials_data;
479  for(const auto& cred : credentials_) {
480  credentials_data.push_back(pref_constants::CREDENTIAL_SEPARATOR);
481  credentials_data.insert(credentials_data.end(), cred.username.begin(), cred.username.end());
482  credentials_data.push_back('@');
483  credentials_data.insert(credentials_data.end(), cred.server.begin(), cred.server.end());
484  credentials_data.push_back('=');
485  preferences::secure_buffer key_escaped = escape(cred.key);
486  credentials_data.insert(credentials_data.end(), key_escaped.begin(), key_escaped.end());
487  }
488  try {
490  preferences::secure_buffer encrypted = aes_encrypt(credentials_data, build_key("global", get_system_username()));
491  credentials_file->write(reinterpret_cast<const char*>(encrypted.data()), encrypted.size());
492  } catch(const filesystem::io_exception&) {
493  ERR_CFG << "error writing to credentials file '" << filesystem::get_credentials_file() << "'";
494  }
495 
496 #ifndef _WIN32
497  if(!creds_file_existed) {
498  if(chmod(filesystem::get_credentials_file().c_str(), 0600) == -1) {
499  ERR_FS << "error setting permissions of credentials file '" << filesystem::get_credentials_file() << "'";
500  }
501  }
502 #endif
503 }
504 
505 //
506 // helpers
507 //
508 void prefs::set_child(const std::string& key, const config& val) {
510  preferences_.add_child(key, val);
511 }
512 
514 {
515  return preferences_.optional_child(key);
516 }
517 
518 std::string prefs::get(const std::string& key, const std::string& def) {
519  return preferences_[key].empty() ? def : preferences_[key];
520 }
521 
523 {
524  return preferences_[key];
525 }
526 
527 //
528 // accessors
529 //
530 static std::string fix_orb_color_name(const std::string& color) {
531  if (color.substr(0,4) == "orb_") {
532  if(color[4] >= '0' && color[4] <= '9') {
533  return color.substr(5);
534  } else {
535  return color.substr(4);
536  }
537  }
538  return color;
539 }
540 
541 std::string prefs::allied_color() {
542  std::string ally_color = preferences_[prefs_list::ally_orb_color].str();
543  if (ally_color.empty())
545  return fix_orb_color_name(ally_color);
546 }
547 void prefs::set_allied_color(const std::string& color_id) {
549 }
550 
551 std::string prefs::enemy_color() {
553  if (enemy_color.empty())
556 }
557 void prefs::set_enemy_color(const std::string& color_id) {
559 }
560 
561 std::string prefs::moved_color() {
563  if (moved_color.empty())
566 }
567 void prefs::set_moved_color(const std::string& color_id) {
569 }
570 
571 std::string prefs::unmoved_color() {
573  if (unmoved_color.empty())
576 }
577 void prefs::set_unmoved_color(const std::string& color_id) {
579 }
580 
581 std::string prefs::partial_color() {
582  std::string partmoved_color = preferences_[prefs_list::partial_orb_color].str();
583  if (partmoved_color.empty())
585  return fix_orb_color_name(partmoved_color);
586 }
587 void prefs::set_partial_color(const std::string& color_id) {
589 }
590 std::string prefs::reach_map_color() {
591  std::string reachmap_color = preferences_[prefs_list::reach_map_color].str();
592  if (reachmap_color.empty())
594  return fix_orb_color_name(reachmap_color);
595 }
596 void prefs::set_reach_map_color(const std::string& color_id) {
598 }
599 
601  std::string reachmap_enemy_color = preferences_[prefs_list::reach_map_enemy_color].str();
602  if (reachmap_enemy_color.empty())
604  return fix_orb_color_name(reachmap_enemy_color);
605 }
606 void prefs::set_reach_map_enemy_color(const std::string& color_id) {
608 }
609 
611 {
613 }
614 
615 void prefs::set_reach_map_border_opacity(const int new_opacity)
616 {
618 }
619 
621 {
623 }
624 
625 void prefs::set_reach_map_tint_opacity(const int new_opacity)
626 {
628 }
629 
631 {
632  const unsigned x_res = preferences_[prefs_list::xresolution].to_unsigned();
633  const unsigned y_res = preferences_[prefs_list::yresolution].to_unsigned();
634 
635  // Either resolution was unspecified, return default.
636  if(x_res == 0 || y_res == 0) {
638  }
639 
640  return point(
641  std::max<unsigned>(x_res, pref_constants::min_window_width),
642  std::max<unsigned>(y_res, pref_constants::min_window_height)
643  );
644 }
645 
646 void prefs::set_resolution(const point& res)
647 {
648  preferences_[prefs_list::xresolution] = std::to_string(res.x);
649  preferences_[prefs_list::yresolution] = std::to_string(res.y);
650 }
651 
653 {
654  // For now this has a minimum value of 1 and a maximum of 4.
655  return std::max<int>(std::min<int>(preferences_[prefs_list::pixel_scale].to_int(1), pref_constants::max_pixel_scale), pref_constants::min_pixel_scale);
656 }
657 
659 {
661 }
662 
664 {
665  if(video::headless()) {
666  return true;
667  }
668 
669  return preferences_[prefs_list::turbo].to_bool();
670 }
671 
672 void prefs::set_turbo(bool ison)
673 {
674  preferences_[prefs_list::turbo] = ison;
675 }
676 
678 {
679  // Clip at 80 because if it's too low it'll cause crashes
680  return std::max<int>(std::min<int>(preferences_[prefs_list::font_scale].to_int(100), pref_constants::max_font_scaling), pref_constants::min_font_scaling);
681 }
682 
684 {
686 }
687 
689 {
690  return (size * font_scaling()) / 100;
691 }
692 
694 {
695  return preferences_[prefs_list::keepalive_timeout].to_int(20);
696 }
697 
698 void prefs::keepalive_timeout(int seconds)
699 {
700  preferences_[prefs_list::keepalive_timeout] = std::abs(seconds);
701 }
702 
704 {
705  // Sounds don't sound good on Windows unless the buffer size is 4k,
706  // but this seems to cause crashes on other systems...
707  #ifdef _WIN32
708  const std::size_t buf_size = 4096;
709  #else
710  const std::size_t buf_size = 1024;
711  #endif
712 
713  return preferences_[prefs_list::sound_buffer_size].to_int(buf_size);
714 }
715 
716 void prefs::save_sound_buffer_size(const std::size_t size)
717 {
718  const std::string new_size = std::to_string(size);
719  if (preferences_[prefs_list::sound_buffer_size] == new_size)
720  return;
721 
722  preferences_[prefs_list::sound_buffer_size] = new_size;
723 
725 }
726 
728 {
729  return sound::volume::from_percent(preferences_[prefs_list::music_volume].to_double(100.f));
730 }
731 
733 {
734  if(music_volume() == vol) {
735  return;
736  }
737 
738  preferences_[prefs_list::music_volume] = vol.as_percent();
740 }
741 
743 {
744  return sound::volume::from_percent(preferences_[prefs_list::sound_volume].to_double(100.f));
745 }
746 
748 {
749  if(sound_volume() == vol) {
750  return;
751  }
752 
753  preferences_[prefs_list::sound_volume] = vol.as_percent();
755 }
756 
758 {
759  return sound::volume::from_percent(preferences_[prefs_list::bell_volume].to_double(100.f));
760 }
761 
763 {
764  if(bell_volume() == vol) {
765  return;
766  }
767 
768  preferences_[prefs_list::bell_volume] = vol.as_percent();
770 }
771 
772 // old pref name had uppercase UI
774 {
775  if(preferences_.has_attribute(prefs_list::ui_volume)) {
776  return sound::volume::from_percent(preferences_[prefs_list::ui_volume].to_double(100.f));
777  } else {
778  return sound::volume::from_percent(preferences_["UI_volume"].to_double(100.f));
779  }
780 }
781 
783 {
784  if(ui_volume() == vol) {
785  return;
786  }
787 
788  preferences_[prefs_list::ui_volume] = vol.as_percent();
790 }
791 
793 {
794  return preferences_[prefs_list::turn_bell].to_bool(true);
795 }
796 
797 bool prefs::set_turn_bell(bool ison)
798 {
799  if(!turn_bell() && ison) {
801  if(!music_on() && !sound() && !ui_sound_on()) {
802  if(!sound::init_sound()) {
804  return false;
805  }
806  }
807  } else if(turn_bell() && !ison) {
810  if(!music_on() && !sound() && !ui_sound_on())
812  }
813  return true;
814 }
815 
816 // old pref name had uppercase UI
818 {
819  if(preferences_.has_attribute(prefs_list::ui_sound)) {
820  return preferences_[prefs_list::ui_sound].to_bool(true);
821  } else {
822  return preferences_["UI_sound"].to_bool(true);
823  }
824 }
825 
826 bool prefs::set_ui_sound(bool ison)
827 {
828  if(!ui_sound_on() && ison) {
829  preferences_[prefs_list::ui_sound] = true;
830  if(!music_on() && !sound() && !turn_bell()) {
831  if(!sound::init_sound()) {
832  preferences_[prefs_list::ui_sound] = false;
833  return false;
834  }
835  }
836  } else if(ui_sound_on() && !ison) {
837  preferences_[prefs_list::ui_sound] = false;
839  if(!music_on() && !sound() && !turn_bell())
841  }
842  return true;
843 }
844 
846 {
847  return preferences_[prefs_list::message_bell].to_bool(true);
848 }
849 
851 {
852  return preferences_[prefs_list::sound].to_bool(true);
853 }
854 
855 bool prefs::set_sound(bool ison) {
856  if(!sound() && ison) {
857  preferences_[prefs_list::sound] = true;
858  if(!music_on() && !turn_bell() && !ui_sound_on()) {
859  if(!sound::init_sound()) {
860  preferences_[prefs_list::sound] = false;
861  return false;
862  }
863  }
864  } else if(sound() && !ison) {
865  preferences_[prefs_list::sound] = false;
867  if(!music_on() && !turn_bell() && !ui_sound_on())
869  }
870  return true;
871 }
872 
874 {
875  return preferences_[prefs_list::music].to_bool(true);
876 }
877 
878 bool prefs::set_music(bool ison) {
879  if(!music_on() && ison) {
880  preferences_[prefs_list::music] = true;
881  if(!sound() && !turn_bell() && !ui_sound_on()) {
882  if(!sound::init_sound()) {
883  preferences_[prefs_list::music] = false;
884  return false;
885  }
886  }
887  else
889  } else if(music_on() && !ison) {
890  preferences_[prefs_list::music] = false;
891  if(!sound() && !turn_bell() && !ui_sound_on())
893  else
895  }
896  return true;
897 }
898 
900 {
901  return std::clamp<int>(preferences_[prefs_list::scroll].to_int(50), 1, 100);
902 }
903 
904 void prefs::set_scroll_speed(const int new_speed)
905 {
906  preferences_[prefs_list::scroll] = new_speed;
907 }
908 
910 {
911  return preferences_[prefs_list::middle_click_scrolls].to_bool(true);
912 }
913 
915 {
916  return preferences_[prefs_list::scroll_threshold].to_int(10);
917 }
918 
920 {
921  return fps_;
922 }
923 
924 void prefs::set_show_fps(bool value)
925 {
926  fps_ = value;
927 }
928 
930 {
932 }
933 
935 {
937 }
938 
940 {
942  preferences_.clear_children("hotkey");
943 }
944 
945 void prefs::add_alias(const std::string &alias, const std::string &command)
946 {
947  config &alias_list = preferences_.child_or_add("alias");
948  alias_list[alias] = command;
949 }
950 
951 
953 {
954  return get_child(prefs_list::alias);
955 }
956 
957 unsigned int prefs::sample_rate()
958 {
959  return preferences_[prefs_list::sample_rate].to_int(44100);
960 }
961 
962 void prefs::save_sample_rate(const unsigned int rate)
963 {
964  if (sample_rate() == rate)
965  return;
966 
967  preferences_[prefs_list::sample_rate] = rate;
968 
969  // If audio is open, we have to re set sample rate
971 }
972 
974 {
975  return preferences_[prefs_list::confirm_load_save_from_different_version].to_bool(true);
976 }
977 
979 {
980  return preferences_[prefs_list::use_twelve_hour_clock_format].to_bool();
981 }
982 
984 {
985  return sort_order::get_enum(preferences_[prefs_list::addon_manager_saved_order_direction].to_int()).value_or(sort_order::type::none);
986 }
987 
989 {
990  preferences_[prefs_list::addon_manager_saved_order_direction] = sort_order::get_string(value);
991 }
992 
993 bool prefs::achievement(const std::string& content_for, const std::string& id)
994 {
995  for(config& ach : preferences_.child_range(prefs_list::achievements))
996  {
997  if(ach["content_for"].str() == content_for)
998  {
999  return utils::contains(utils::split(ach["ids"]), id);
1000  }
1001  }
1002  return false;
1003 }
1004 
1005 void prefs::set_achievement(const std::string& content_for, const std::string& id)
1006 {
1007  for(config& ach : preferences_.child_range(prefs_list::achievements))
1008  {
1009  // if achievements already exist for this content and the achievement has not already been set, add it
1010  if(ach["content_for"].str() == content_for)
1011  {
1012  std::vector<std::string> ids = utils::split(ach["ids"]);
1013 
1014  if(ids.empty())
1015  {
1016  ach["ids"] = id;
1017  }
1018  else if(!utils::contains(ids, id))
1019  {
1020  ach["ids"] = ach["ids"].str() + "," + id;
1021  }
1022  ach.remove_children("in_progress", [&id](config cfg){return cfg["id"].str() == id;});
1023  return;
1024  }
1025  }
1026 
1027  // else no achievements have been set for this content yet
1028  config ach;
1029  ach["content_for"] = content_for;
1030  ach["ids"] = id;
1031  preferences_.add_child(prefs_list::achievements, ach);
1032 }
1033 
1034 int prefs::progress_achievement(const std::string& content_for, const std::string& id, int limit, int max_progress, int amount)
1035 {
1036  if(achievement(content_for, id))
1037  {
1038  return -1;
1039  }
1040 
1041  for(config& ach : preferences_.child_range(prefs_list::achievements))
1042  {
1043  // if achievements already exist for this content and the achievement has not already been set, add it
1044  if(ach["content_for"].str() == content_for)
1045  {
1046  // check if this achievement has progressed before - if so then increment it
1047  for(config& in_progress : ach.child_range("in_progress"))
1048  {
1049  if(in_progress["id"].str() == id)
1050  {
1051  // don't let using 'limit' decrease the achievement's current progress
1052  int starting_progress = in_progress["progress_at"].to_int();
1053  if(starting_progress >= limit) {
1054  return starting_progress;
1055  }
1056 
1057  in_progress["progress_at"] = std::clamp(starting_progress + amount, 0, std::min(limit, max_progress));
1058  return in_progress["progress_at"].to_int();
1059  }
1060  }
1061 
1062  // else this is the first time this achievement is progressing
1063  if(amount != 0)
1064  {
1065  config set_progress;
1066  set_progress["id"] = id;
1067  set_progress["progress_at"] = std::clamp(amount, 0, std::min(limit, max_progress));
1068 
1069  config& child = ach.add_child("in_progress", set_progress);
1070  return child["progress_at"].to_int();
1071  }
1072  return 0;
1073  }
1074  }
1075 
1076  // else not only has this achievement not progressed before, this is the first achievement for this achievement group to be added
1077  if(amount != 0)
1078  {
1079  config ach;
1080  config set_progress;
1081 
1082  set_progress["id"] = id;
1083  set_progress["progress_at"] = std::clamp(amount, 0, std::min(limit, max_progress));
1084 
1085  ach["content_for"] = content_for;
1086  ach["ids"] = "";
1087 
1088  config& child = ach.add_child("in_progress", set_progress);
1089  preferences_.add_child(prefs_list::achievements, ach);
1090  return child["progress_at"].to_int();
1091  }
1092  return 0;
1093 }
1094 
1095 bool prefs::sub_achievement(const std::string& content_for, const std::string& id, const std::string& sub_id)
1096 {
1097  // this achievement is already completed
1098  if(achievement(content_for, id))
1099  {
1100  return true;
1101  }
1102 
1103  for(config& ach : preferences_.child_range(prefs_list::achievements))
1104  {
1105  if(ach["content_for"].str() == content_for)
1106  {
1107  // check if the specific sub-achievement has been completed but the overall achievement is not completed
1108  for(const auto& in_progress : ach.child_range("in_progress"))
1109  {
1110  if(in_progress["id"] == id)
1111  {
1112  return utils::contains(utils::split(in_progress["sub_ids"]), sub_id);
1113  }
1114  }
1115  }
1116  }
1117  return false;
1118 }
1119 
1120 void prefs::set_sub_achievement(const std::string& content_for, const std::string& id, const std::string& sub_id)
1121 {
1122  // this achievement is already completed
1123  if(achievement(content_for, id))
1124  {
1125  return;
1126  }
1127 
1128  for(config& ach : preferences_.child_range(prefs_list::achievements))
1129  {
1130  // if achievements already exist for this content and the achievement has not already been set, add it
1131  if(ach["content_for"].str() == content_for)
1132  {
1133  // check if this achievement has had sub-achievements set before
1134  for(config& in_progress : ach.child_range("in_progress"))
1135  {
1136  if(in_progress["id"].str() == id)
1137  {
1138  std::vector<std::string> sub_ids = utils::split(in_progress["sub_ids"]);
1139 
1140  if(!utils::contains(sub_ids, sub_id))
1141  {
1142  in_progress["sub_ids"] = in_progress["sub_ids"].str() + "," + sub_id;
1143  }
1144 
1145  in_progress["progress_at"] = sub_ids.size()+1;
1146  return;
1147  }
1148  }
1149 
1150  // else if this is the first sub-achievement being set
1151  config set_progress;
1152  set_progress["id"] = id;
1153  set_progress["sub_ids"] = sub_id;
1154  set_progress["progress_at"] = 1;
1155  ach.add_child("in_progress", set_progress);
1156  return;
1157  }
1158  }
1159 
1160  // else not only has this achievement not had a sub-achievement completed before, this is the first achievement for this achievement group to be added
1161  config ach;
1162  config set_progress;
1163 
1164  set_progress["id"] = id;
1165  set_progress["sub_ids"] = sub_id;
1166  set_progress["progress_at"] = 1;
1167 
1168  ach["content_for"] = content_for;
1169  ach["ids"] = "";
1170 
1171  ach.add_child("in_progress", set_progress);
1172  preferences_.add_child(prefs_list::achievements, ach);
1173 }
1174 
1176 {
1177  return preferences_[prefs_list::show_deprecation].to_bool(def);
1178 }
1179 
1181 {
1182  return preferences_[prefs_list::scroll_when_mouse_outside].to_bool(def);
1183 }
1184 
1186 {
1187  set_child(prefs_list::dir_bookmarks, cfg);
1188 }
1190 {
1191  return get_child(prefs_list::dir_bookmarks);
1192 }
1193 
1195 {
1196  return preferences_[prefs_list::lobby_auto_open_whisper_windows].to_bool(true);
1197 }
1198 
1200 {
1201  return std::max(std::size_t(1), preferences_[prefs_list::editor_max_recent_files].to_size_t(10));
1202 }
1203 
1204 //
1205 // NOTE: The MRU read/save functions enforce the entry count limit in
1206 // order to ensure the list on disk doesn't grow forever. Otherwise,
1207 // normally this would be the UI's responsibility instead.
1208 //
1209 
1210 std::vector<std::string> prefs::do_read_editor_mru()
1211 {
1212  auto cfg = get_child(prefs_list::editor_recent_files);
1213 
1214  std::vector<std::string> mru;
1215  if(!cfg) {
1216  return mru;
1217  }
1218 
1219  for(const config& child : cfg->child_range("entry"))
1220  {
1221  const std::string& entry = child["path"].str();
1222  if(!entry.empty()) {
1223  mru.push_back(entry);
1224  }
1225  }
1226 
1227  mru.resize(std::min(editor_mru_limit(), mru.size()));
1228 
1229  return mru;
1230 }
1231 
1232 void prefs::do_commit_editor_mru(const std::vector<std::string>& mru)
1233 {
1234  config cfg;
1235  unsigned n = 0;
1236 
1237  for(const std::string& entry : mru)
1238  {
1239  if(entry.empty()) {
1240  continue;
1241  }
1242 
1243  config& child = cfg.add_child("entry");
1244  child["path"] = entry;
1245 
1246  if(++n >= editor_mru_limit()) {
1247  break;
1248  }
1249  }
1250 
1251  set_child(prefs_list::editor_recent_files, cfg);
1252 }
1253 
1254 std::vector<std::string> prefs::recent_files()
1255 {
1256  return do_read_editor_mru();
1257 }
1258 
1259 void prefs::add_recent_files_entry(const std::string& path)
1260 {
1261  if(path.empty()) {
1262  return;
1263  }
1264 
1265  std::vector<std::string> mru = do_read_editor_mru();
1266 
1267  // Enforce uniqueness. Normally shouldn't do a thing unless somebody
1268  // has been tampering with the preferences file.
1269  utils::erase(mru, path);
1270 
1271  mru.insert(mru.begin(), path);
1272  mru.resize(std::min(editor_mru_limit(), mru.size()));
1273 
1274  do_commit_editor_mru(mru);
1275 }
1276 
1278 {
1279  return preferences_[prefs_list::color_cursors].to_bool(true);
1280 }
1281 
1283 {
1284  preferences_[prefs_list::color_cursors] = value;
1285 
1286  cursor::set();
1287 }
1288 
1290 {
1291  return preferences_[prefs_list::unit_standing_animations].to_bool(true);
1292 }
1293 
1295 {
1296  preferences_[prefs_list::unit_standing_animations] = value;
1297 
1298  if(display* d = display::get_singleton()) {
1299  d->reset_standing_animations();
1300  }
1301 }
1302 
1304 {
1305  std::vector<theme_info> themes = theme::get_basic_theme_info();
1306 
1307  if (themes.empty()) {
1309  _("No known themes. Try changing from within an existing game."));
1310 
1311  return false;
1312  }
1313 
1314  gui2::dialogs::theme_list dlg(themes);
1315 
1316  for (std::size_t k = 0; k < themes.size(); ++k) {
1317  if(themes[k].id == theme()) {
1318  dlg.set_selected_index(static_cast<int>(k));
1319  }
1320  }
1321 
1322  dlg.show();
1323  const int action = dlg.selected_index();
1324 
1325  if (action >= 0) {
1326  set_theme(themes[action].id);
1327  if(display::get_singleton() && resources::gamedata && resources::gamedata->get_theme().empty()) {
1328  display::get_singleton()->set_theme(themes[action].id);
1329  }
1330 
1331  return true;
1332  }
1333 
1334  return false;
1335 }
1336 
1338 {
1339  const std::string filename = filesystem::get_wesnothd_name();
1340 
1341  const std::string& old_path = filesystem::directory_name(get_mp_server_program_name());
1342  std::string path =
1343  !old_path.empty() && filesystem::is_directory(old_path)
1344  ? old_path : filesystem::get_exe_dir();
1345 
1346  const std::string msg = VGETTEXT("The <b>$filename</b> server application provides multiplayer server functionality and is required for hosting local network games. It will normally be found in the same folder as the game executable.", {{"filename", filename}});
1347 
1349 
1350  dlg.set_title(_("Find Server Application"))
1351  .set_message(msg)
1352  .set_ok_label(_("Select"))
1353  .set_read_only(true)
1355  .set_path(path);
1356 
1357  if(dlg.show()) {
1358  path = dlg.path();
1360  }
1361 }
1362 
1363 std::string prefs::theme()
1364 {
1365  if(video::headless()) {
1366  static const std::string null_theme = "null";
1367  return null_theme;
1368  }
1369 
1370  std::string res = preferences_[prefs_list::theme];
1371  if(res.empty()) {
1372  return "Default";
1373  }
1374 
1375  return res;
1376 }
1377 
1378 void prefs::set_theme(const std::string& theme)
1379 {
1380  if(theme != "null") {
1381  preferences_[prefs_list::theme] = theme;
1382  }
1383 }
1384 
1385 void prefs::set_mp_server_program_name(const std::string& path)
1386 {
1387  if(path.empty()) {
1388  preferences_.remove_attribute(prefs_list::mp_server_program_name);
1389  } else {
1390  preferences_[prefs_list::mp_server_program_name] = path;
1391  }
1392 }
1393 
1395 {
1396  return preferences_[prefs_list::mp_server_program_name].str();
1397 }
1398 
1399 const std::map<std::string, preferences::acquaintance>& prefs::get_acquaintances()
1400 {
1401  return acquaintances_;
1402 }
1403 
1404 const std::string prefs::get_ignored_delim()
1405 {
1406  std::vector<std::string> ignored;
1407 
1408  for(const auto& person : acquaintances_) {
1409  if(person.second.get_status() == "ignore") {
1410  ignored.push_back(person.second.get_nick());
1411  }
1412  }
1413 
1414  return utils::join(ignored);
1415 }
1416 
1417 // returns acquaintances in the form nick => notes where the status = filter
1418 std::map<std::string, std::string> prefs::get_acquaintances_nice(const std::string& filter)
1419 {
1420  std::map<std::string, std::string> ac_nice;
1421 
1422  for(const auto& a : acquaintances_) {
1423  if(a.second.get_status() == filter) {
1424  ac_nice[a.second.get_nick()] = a.second.get_notes();
1425  }
1426  }
1427 
1428  return ac_nice;
1429 }
1430 
1431 std::pair<preferences::acquaintance*, bool> prefs::add_acquaintance(const std::string& nick, const std::string& mode, const std::string& notes)
1432 {
1433  if(!utils::isvalid_wildcard(nick)) {
1434  return std::pair(nullptr, false);
1435  }
1436 
1437  preferences::acquaintance new_entry(nick, mode, notes);
1438  auto [iter, added_new] = acquaintances_.insert_or_assign(nick, new_entry);
1439 
1440  return std::pair(&iter->second, added_new);
1441 }
1442 
1443 bool prefs::remove_acquaintance(const std::string& nick)
1444 {
1446 
1447  // nick might include the notes, depending on how we're removing
1448  if(i == acquaintances_.end()) {
1449  std::size_t pos = nick.find_first_of(' ');
1450 
1451  if(pos != std::string::npos) {
1452  i = acquaintances_.find(nick.substr(0, pos));
1453  }
1454  }
1455 
1456  if(i == acquaintances_.end()) {
1457  return false;
1458  }
1459 
1460  acquaintances_.erase(i);
1461 
1462  return true;
1463 }
1464 
1465 bool prefs::is_friend(const std::string& nick)
1466 {
1467  const auto it = acquaintances_.find(nick);
1468 
1469  if(it == acquaintances_.end()) {
1470  return false;
1471  } else {
1472  return it->second.get_status() == "friend";
1473  }
1474 }
1475 
1476 bool prefs::is_ignored(const std::string& nick)
1477 {
1478  const auto it = acquaintances_.find(nick);
1479 
1480  if(it == acquaintances_.end()) {
1481  return false;
1482  } else {
1483  return it->second.get_status() == "ignore";
1484  }
1485 }
1486 
1487 void prefs::add_completed_campaign(const std::string& campaign_id, const std::string& difficulty_level)
1488 {
1489  completed_campaigns_[campaign_id].insert(difficulty_level);
1490 }
1491 
1492 bool prefs::is_campaign_completed(const std::string& campaign_id)
1493 {
1494  return completed_campaigns_.count(campaign_id) != 0;
1495 }
1496 
1497 bool prefs::is_campaign_completed(const std::string& campaign_id, const std::string& difficulty_level)
1498 {
1499  const auto it = completed_campaigns_.find(campaign_id);
1500  return it == completed_campaigns_.end() ? false : it->second.count(difficulty_level) != 0;
1501 }
1502 
1503 bool prefs::parse_should_show_lobby_join(const std::string& sender, const std::string& message)
1504 {
1505  // If it's actually not a lobby join or leave message return true (show it).
1506  if(sender != "server") {
1507  return true;
1508  }
1509 
1510  std::string::size_type pos = message.find(" has logged into the lobby");
1511  if(pos == std::string::npos) {
1512  pos = message.find(" has disconnected");
1513  if(pos == std::string::npos) {
1514  return true;
1515  }
1516  }
1517 
1520  return false;
1521  }
1522 
1524  return true;
1525  }
1526 
1527  return is_friend(message.substr(0, pos));
1528 }
1529 
1531 {
1532  std::string pref = preferences_[prefs_list::lobby_joins];
1533  if(pref == "friends") {
1535  } else if(pref == "all") {
1537  } else if(pref == "none") {
1539  } else {
1541  }
1542 }
1543 
1545 {
1552  }
1553 }
1554 
1555 const std::vector<game_config::server_info>& prefs::builtin_servers_list()
1556 {
1557  static std::vector<game_config::server_info> pref_servers = game_config::server_list;
1558  return pref_servers;
1559 }
1560 
1561 void prefs::add_game_preset(config&& preset_data)
1562 {
1563  config preset{ prefs_list::game_preset, std::move(preset_data) };
1564 
1565  int min = 0;
1566  for(const auto& c : preferences_.child_range(prefs_list::game_preset)) {
1567  min = std::min(min, c["id"].to_int());
1568  }
1569  preset.mandatory_child(prefs_list::game_preset)["id"] = min-1;
1570  preferences_.append(preset);
1571 }
1572 
1574 {
1575  preferences_.remove_children(prefs_list::game_preset, [&id](const config& preset) { return preset["id"].to_int() == id; });
1576 }
1577 
1579 {
1580  return preferences_.child_range(prefs_list::game_preset);
1581 }
1582 
1584 {
1585  return preferences_.find_child(prefs_list::game_preset, "id", std::to_string(id));
1586 }
1587 
1588 std::vector<game_config::server_info> prefs::user_servers_list()
1589 {
1590  std::vector<game_config::server_info> pref_servers;
1591 
1592  for(const config& server : preferences_.child_range(prefs_list::server)) {
1593  pref_servers.emplace_back();
1594  pref_servers.back().name = server["name"].str();
1595  pref_servers.back().address = server["address"].str();
1596  }
1597 
1598  return pref_servers;
1599 }
1600 
1601 void prefs::set_user_servers_list(const std::vector<game_config::server_info>& value)
1602 {
1603  preferences_.clear_children(prefs_list::server);
1604 
1605  for(const auto& svinfo : value) {
1606  config& sv_cfg = preferences_.add_child(prefs_list::server);
1607  sv_cfg["name"] = svinfo.name;
1608  sv_cfg["address"] = svinfo.address;
1609  }
1610 }
1611 
1612 std::string prefs::network_host()
1613 {
1614  std::string res = preferences_[prefs_list::host];
1615  if(res.empty()) {
1616  return builtin_servers_list().front().address;
1617  } else {
1618  return res;
1619  }
1620 }
1621 
1622 void prefs::set_network_host(const std::string& host)
1623 {
1624  preferences_[prefs_list::host] = host;
1625 }
1626 
1628 {
1629  if(!preferences_[prefs_list::campaign_server].empty()) {
1630  return preferences_[prefs_list::campaign_server].str();
1631  } else {
1633  }
1634 }
1635 
1636 void prefs::set_campaign_server(const std::string& host)
1637 {
1638  preferences_[prefs_list::campaign_server] = host;
1639 }
1640 
1642 {
1643  return preferences_[prefs_list::show_combat].to_bool(true);
1644 }
1645 
1647 {
1648  if(options_initialized_) {
1649  return option_values_;
1650  }
1651 
1652  if(!get_child(prefs_list::options)) {
1653  // It may be an invalid config, which would cause problems in
1654  // multiplayer_create, so let's replace it with an empty but valid
1655  // config
1657  } else {
1658  option_values_ = *get_child(prefs_list::options);
1659  }
1660 
1661  options_initialized_ = true;
1662 
1663  return option_values_;
1664 }
1665 
1667 {
1668  set_child(prefs_list::options, values);
1669  options_initialized_ = false;
1670 }
1671 
1672 std::chrono::seconds prefs::countdown_init_time()
1673 {
1674  return chrono::parse_duration(preferences_[prefs_list::mp_countdown_init_time], 240s);
1675 }
1676 
1677 void prefs::set_countdown_init_time(const std::chrono::seconds& value)
1678 {
1679  preferences_[prefs_list::mp_countdown_init_time] = std::clamp(value, 0s, 1500s);
1680 }
1681 
1683 {
1684  preferences_.remove_attribute(prefs_list::mp_countdown_init_time);
1685 }
1686 
1687 std::chrono::seconds prefs::countdown_reservoir_time()
1688 {
1689  return chrono::parse_duration(preferences_[prefs_list::mp_countdown_reservoir_time], 360s);
1690 }
1691 
1692 void prefs::set_countdown_reservoir_time(const std::chrono::seconds& value)
1693 {
1694  preferences_[prefs_list::mp_countdown_reservoir_time] = std::clamp(value, 30s, 1500s);
1695 }
1696 
1698 {
1699  preferences_.remove_attribute(prefs_list::mp_countdown_reservoir_time);
1700 }
1701 
1702 std::chrono::seconds prefs::countdown_turn_bonus()
1703 {
1704  return chrono::parse_duration(preferences_[prefs_list::mp_countdown_turn_bonus], 240s);
1705 }
1706 
1707 void prefs::set_countdown_turn_bonus(const std::chrono::seconds& value)
1708 {
1709  preferences_[prefs_list::mp_countdown_turn_bonus] = std::clamp(value, 0s, 300s);
1710 }
1711 
1713 {
1714  preferences_.remove_attribute(prefs_list::mp_countdown_turn_bonus);
1715 }
1716 
1717 std::chrono::seconds prefs::countdown_action_bonus()
1718 {
1719  return chrono::parse_duration(preferences_[prefs_list::mp_countdown_action_bonus], 0s);
1720 }
1721 
1722 void prefs::set_countdown_action_bonus(const std::chrono::seconds& value)
1723 {
1724  preferences_[prefs_list::mp_countdown_action_bonus] = std::clamp(value, 0s, 30s);
1725 }
1726 
1728 {
1729  preferences_.remove_attribute(prefs_list::mp_countdown_action_bonus);
1730 }
1731 
1732 std::chrono::minutes prefs::chat_message_aging()
1733 {
1734  return chrono::parse_duration(preferences_[prefs_list::chat_message_aging], 20min);
1735 }
1736 
1737 void prefs::set_chat_message_aging(const std::chrono::minutes& value)
1738 {
1739  preferences_[prefs_list::chat_message_aging] = value;
1740 }
1741 
1743 {
1744  return settings::get_village_gold(preferences_[prefs_list::mp_village_gold]);
1745 }
1746 
1748 {
1749  preferences_[prefs_list::mp_village_gold] = value;
1750 }
1751 
1753 {
1754  return settings::get_village_support(preferences_[prefs_list::mp_village_support]);
1755 }
1756 
1758 {
1759  preferences_[prefs_list::mp_village_support] = std::to_string(value);
1760 }
1761 
1763 {
1764  return settings::get_xp_modifier(preferences_[prefs_list::mp_xp_modifier]);
1765 }
1766 
1767 void prefs::set_xp_modifier(int value)
1768 {
1769  preferences_[prefs_list::mp_xp_modifier] = value;
1770 }
1771 
1772 const std::vector<std::string>& prefs::modifications(bool mp)
1773 {
1775  if(mp) {
1776  mp_modifications_ = utils::split(preferences_[prefs_list::mp_modifications].str(), ',');
1778  } else {
1779  sp_modifications_ = utils::split(preferences_[prefs_list::sp_modifications].str(), ',');
1781  }
1782  }
1783 
1785 }
1786 
1787 void prefs::set_modifications(const std::vector<std::string>& value, bool mp)
1788 {
1789  if(mp) {
1790  preferences_[prefs_list::mp_modifications] = utils::join(value, ",");
1792  } else {
1793  preferences_[prefs_list::sp_modifications] = utils::join(value, ",");
1795  }
1796 }
1797 
1799 {
1800  return message_private_on_;
1801 }
1802 
1804 {
1805  message_private_on_ = value;
1806 }
1807 
1809 {
1810  const std::string& choice = preferences_[prefs_list::compress_saves];
1811 
1812  // "yes" was used in 1.11.7 and earlier; the compress_saves
1813  // option used to be a toggle for gzip in those versions.
1814  if(choice.empty() || choice == "gzip" || choice == "yes") {
1816  } else if(choice == "bzip2") {
1818  } else if(choice == "none" || choice == "no") { // see above
1820  } /*else*/
1821 
1822  // In case the preferences file was created by a later version
1823  // supporting some algorithm we don't; although why would anyone
1824  // playing a game need more algorithms, really...
1826 }
1827 
1828 std::string prefs::get_chat_timestamp(const std::chrono::system_clock::time_point& t)
1829 {
1830  if(chat_timestamp()) {
1831  if(use_twelve_hour_clock_format() == false) {
1832  return chrono::format_local_timestamp(t, _("[%H:%M]")) + " ";
1833  } else {
1834  return chrono::format_local_timestamp(t, _("[%I:%M %p]")) + " ";
1835  }
1836  }
1837 
1838  return "";
1839 }
1840 
1841 std::set<std::string>& prefs::encountered_units()
1842 {
1843  return encountered_units_set_;
1844 }
1845 
1846 std::set<t_translation::terrain_code>& prefs::encountered_terrains()
1847 {
1849 }
1850 
1851 /**
1852  * Returns a pointer to the history vector associated with given id
1853  * making a new one if it doesn't exist.
1854  *
1855  * @todo FIXME only used for gui2. Could be used for the above histories.
1856  */
1857 std::vector<std::string>* prefs::get_history(const std::string& id)
1858 {
1859  return &history_map_[id];
1860 }
1861 
1863 {
1864  const std::string confirmation = preferences_[prefs_list::confirm_end_turn];
1865  return confirmation == "green" || confirmation == "yes";
1866 }
1867 
1869 {
1870  return preferences_[prefs_list::confirm_end_turn] == "yellow";
1871 }
1872 
1874 {
1875  // This is very non-intrusive so it is on by default
1876  const std::string confirmation = preferences_[prefs_list::confirm_end_turn];
1877  return confirmation == "no_moves" || confirmation.empty();
1878 }
1879 
1880 void prefs::encounter_recruitable_units(const std::vector<team>& teams)
1881 {
1882  for(const team& help_team : teams) {
1883  help_team.log_recruitable();
1884  encountered_units_set_.insert(help_team.recruits().begin(), help_team.recruits().end());
1885  }
1886 }
1887 
1889 {
1890  for(const auto& help_unit : units) {
1891  encountered_units_set_.insert(help_unit.type_id());
1892  }
1893 }
1894 
1895 void prefs::encounter_recallable_units(const std::vector<team>& teams)
1896 {
1897  for(const team& t : teams) {
1898  for(const unit_const_ptr u : t.recall_list()) {
1899  encountered_units_set_.insert(u->type_id());
1900  }
1901  }
1902 }
1903 
1905 {
1906  map.for_each_loc([&](const map_location& loc) {
1908  });
1909 }
1910 
1912 {
1913  encountered_terrains().insert(terrain.number());
1914  for(const t_translation::terrain_code& t : terrain.union_type()) {
1915  encountered_terrains().insert(t);
1916  }
1917 }
1918 
1920 {
1921  encounter_recruitable_units(gameboard_.teams());
1922  encounter_start_units(gameboard_.units());
1923  encounter_recallable_units(gameboard_.teams());
1924  encounter_map_terrain(gameboard_.map());
1925 }
1926 
1928 {
1929  preferences_.remove_attribute(prefs_list::player_joins_sound);
1930  preferences_.remove_attribute(prefs_list::player_joins_notif);
1931  preferences_.remove_attribute(prefs_list::player_joins_lobby);
1932  preferences_.remove_attribute(prefs_list::player_leaves_sound);
1933  preferences_.remove_attribute(prefs_list::player_leaves_notif);
1934  preferences_.remove_attribute(prefs_list::player_leaves_lobby);
1935  preferences_.remove_attribute(prefs_list::private_message_sound);
1936  preferences_.remove_attribute(prefs_list::private_message_notif);
1937  preferences_.remove_attribute(prefs_list::private_message_lobby);
1938  preferences_.remove_attribute(prefs_list::friend_message_sound);
1939  preferences_.remove_attribute(prefs_list::friend_message_notif);
1940  preferences_.remove_attribute(prefs_list::friend_message_lobby);
1941  preferences_.remove_attribute(prefs_list::public_message_sound);
1942  preferences_.remove_attribute(prefs_list::public_message_notif);
1943  preferences_.remove_attribute(prefs_list::public_message_lobby);
1944  preferences_.remove_attribute(prefs_list::server_message_sound);
1945  preferences_.remove_attribute(prefs_list::server_message_notif);
1946  preferences_.remove_attribute(prefs_list::server_message_lobby);
1947  preferences_.remove_attribute(prefs_list::ready_for_start_sound);
1948  preferences_.remove_attribute(prefs_list::ready_for_start_notif);
1949  preferences_.remove_attribute(prefs_list::ready_for_start_lobby);
1950  preferences_.remove_attribute(prefs_list::game_has_begun_sound);
1951  preferences_.remove_attribute(prefs_list::game_has_begun_notif);
1952  preferences_.remove_attribute(prefs_list::game_has_begun_lobby);
1953  preferences_.remove_attribute(prefs_list::turn_changed_sound);
1954  preferences_.remove_attribute(prefs_list::turn_changed_notif);
1955  preferences_.remove_attribute(prefs_list::turn_changed_lobby);
1956  preferences_.remove_attribute(prefs_list::game_created_sound);
1957  preferences_.remove_attribute(prefs_list::game_created_notif);
1958  preferences_.remove_attribute(prefs_list::game_created_lobby);
1959 }
1960 
1962 {
1963 #ifdef _WIN32
1964  wchar_t buffer[300];
1965  DWORD size = 300;
1966  if(GetUserNameW(buffer, &size)) {
1967  //size includes a terminating null character.
1968  assert(size > 0);
1969  return unicode_cast<std::string>(std::wstring_view{buffer});
1970  }
1971 #else
1972  if(char* const login = getenv("USER")) {
1973  return login;
1974  }
1975 #endif
1976  return {};
1977 }
1978 
1979 preferences::secure_buffer prefs::build_key(const std::string& server, const std::string& login)
1980 {
1981  std::string sysname = get_system_username();
1982  preferences::secure_buffer result(std::max<std::size_t>(server.size() + login.size() + sysname.size(), 32));
1983  unsigned char i = 0;
1984  std::generate(result.begin(), result.end(), [&i]() {return 'x' ^ i++;});
1985  std::copy(login.begin(), login.end(), result.begin());
1986  std::copy(sysname.begin(), sysname.end(), result.begin() + login.size());
1987  std::copy(server.begin(), server.end(), result.begin() + login.size() + sysname.size());
1988  return result;
1989 }
1990 
1992 {
1993 #ifndef __APPLE__
1994  int update_length;
1995  int extra_length;
1996  int total_length;
1997  // AES IV is generally 128 bits
1998  const unsigned char iv[] = {1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8};
1999  unsigned char encrypted_buffer[1024];
2000 
2001  if(plaintext.size() > 1008)
2002  {
2003  ERR_CFG << "Cannot encrypt data larger than 1008 bytes.";
2004  return preferences::secure_buffer();
2005  }
2006  DBG_CFG << "Encrypting data with length: " << plaintext.size();
2007 
2008  EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
2009  if(!ctx)
2010  {
2011  ERR_CFG << "AES EVP_CIPHER_CTX_new failed with error:";
2012  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2013  return preferences::secure_buffer();
2014  }
2015 
2016  // TODO: use EVP_EncryptInit_ex2 once openssl 3.0 is more widespread
2017  if(EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv) != 1)
2018  {
2019  ERR_CFG << "AES EVP_EncryptInit_ex failed with error:";
2020  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2021  EVP_CIPHER_CTX_free(ctx);
2022  return preferences::secure_buffer();
2023  }
2024 
2025  if(EVP_EncryptUpdate(ctx, encrypted_buffer, &update_length, plaintext.data(), plaintext.size()) != 1)
2026  {
2027  ERR_CFG << "AES EVP_EncryptUpdate failed with error:";
2028  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2029  EVP_CIPHER_CTX_free(ctx);
2030  return preferences::secure_buffer();
2031  }
2032  DBG_CFG << "Update length: " << update_length;
2033 
2034  if(EVP_EncryptFinal_ex(ctx, encrypted_buffer + update_length, &extra_length) != 1)
2035  {
2036  ERR_CFG << "AES EVP_EncryptFinal failed with error:";
2037  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2038  EVP_CIPHER_CTX_free(ctx);
2039  return preferences::secure_buffer();
2040  }
2041  DBG_CFG << "Extra length: " << extra_length;
2042 
2043  EVP_CIPHER_CTX_free(ctx);
2044 
2045  total_length = update_length+extra_length;
2047  for(int i = 0; i < total_length; i++)
2048  {
2049  result.push_back(encrypted_buffer[i]);
2050  }
2051 
2052  DBG_CFG << "Successfully encrypted plaintext value of '" << utils::join(plaintext, "") << "' having length " << plaintext.size();
2053  DBG_CFG << "For a total encrypted length of: " << total_length;
2054 
2055  return result;
2056 #else
2057  std::size_t outWritten = 0;
2058  preferences::secure_buffer result(plaintext.size(), '\0');
2059 
2060  CCCryptorStatus ccStatus = CCCrypt(kCCDecrypt,
2061  kCCAlgorithmRC4,
2062  kCCOptionPKCS7Padding,
2063  key.data(),
2064  key.size(),
2065  nullptr,
2066  plaintext.data(),
2067  plaintext.size(),
2068  result.data(),
2069  result.size(),
2070  &outWritten);
2071 
2072  assert(ccStatus == kCCSuccess);
2073  assert(outWritten == plaintext.size());
2074 
2075  return result;
2076 #endif
2077 }
2078 
2080 {
2081 #ifndef __APPLE__
2082  int update_length;
2083  int extra_length;
2084  int total_length;
2085  // AES IV is generally 128 bits
2086  const unsigned char iv[] = {1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8};
2087  unsigned char plaintext_buffer[1024];
2088 
2089  if(encrypted.size() > 1024)
2090  {
2091  ERR_CFG << "Cannot decrypt data larger than 1024 bytes.";
2092  return preferences::secure_buffer();
2093  }
2094  DBG_CFG << "Decrypting data with length: " << encrypted.size();
2095 
2096  EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
2097  if(!ctx)
2098  {
2099  ERR_CFG << "AES EVP_CIPHER_CTX_new failed with error:";
2100  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2101  return preferences::secure_buffer();
2102  }
2103 
2104  // TODO: use EVP_DecryptInit_ex2 once openssl 3.0 is more widespread
2105  if(EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv) != 1)
2106  {
2107  ERR_CFG << "AES EVP_DecryptInit_ex failed with error:";
2108  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2109  EVP_CIPHER_CTX_free(ctx);
2110  return preferences::secure_buffer();
2111  }
2112 
2113  if(EVP_DecryptUpdate(ctx, plaintext_buffer, &update_length, encrypted.data(), encrypted.size()) != 1)
2114  {
2115  ERR_CFG << "AES EVP_DecryptUpdate failed with error:";
2116  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2117  EVP_CIPHER_CTX_free(ctx);
2118  return preferences::secure_buffer();
2119  }
2120  DBG_CFG << "Update length: " << update_length;
2121 
2122  if(EVP_DecryptFinal_ex(ctx, plaintext_buffer + update_length, &extra_length) != 1)
2123  {
2124  ERR_CFG << "AES EVP_DecryptFinal failed with error:";
2125  ERR_CFG << ERR_error_string(ERR_get_error(), nullptr);
2126  EVP_CIPHER_CTX_free(ctx);
2127  return preferences::secure_buffer();
2128  }
2129  DBG_CFG << "Extra length: " << extra_length;
2130 
2131  EVP_CIPHER_CTX_free(ctx);
2132 
2133  total_length = update_length+extra_length;
2135  for(int i = 0; i < total_length; i++)
2136  {
2137  result.push_back(plaintext_buffer[i]);
2138  }
2139 
2140  DBG_CFG << "Successfully decrypted data to the value: " << utils::join(result, "");
2141  DBG_CFG << "For a total decrypted length of: " << total_length;
2142 
2143  return result;
2144 #else
2145  std::size_t outWritten = 0;
2146  preferences::secure_buffer result(encrypted.size(), '\0');
2147 
2148  CCCryptorStatus ccStatus = CCCrypt(kCCDecrypt,
2149  kCCAlgorithmRC4,
2150  kCCOptionPKCS7Padding,
2151  key.data(),
2152  key.size(),
2153  nullptr,
2154  encrypted.data(),
2155  encrypted.size(),
2156  result.data(),
2157  result.size(),
2158  &outWritten);
2159 
2160  assert(ccStatus == kCCSuccess);
2161  assert(outWritten == encrypted.size());
2162 
2163  // the decrypted result is likely shorter than the encrypted data, so the extra padding needs to be removed.
2164  while(!result.empty() && result.back() == 0) {
2165  result.pop_back();
2166  }
2167 
2168  return result;
2169 #endif
2170 }
2171 
2173 {
2174  preferences::secure_buffer unescaped;
2175  unescaped.reserve(text.size());
2176  bool escaping = false;
2177  for(char c : text) {
2178  if(escaping) {
2179  if(c == '\xa') {
2180  unescaped.push_back('\xc');
2181  } else if(c == '.') {
2182  unescaped.push_back('@');
2183  } else {
2184  unescaped.push_back(c);
2185  }
2186  escaping = false;
2187  } else if(c == '\x1') {
2188  escaping = true;
2189  } else {
2190  unescaped.push_back(c);
2191  }
2192  }
2193  assert(!escaping);
2194  return unescaped;
2195 }
2196 
2198 {
2200  escaped.reserve(text.size());
2201  for(char c : text) {
2202  if(c == '\x1') {
2203  escaped.push_back('\x1');
2204  escaped.push_back('\x1');
2205  } else if(c == '\xc') {
2206  escaped.push_back('\x1');
2207  escaped.push_back('\xa');
2208  } else if(c == '@') {
2209  escaped.push_back('\x1');
2210  escaped.push_back('.');
2211  } else {
2212  escaped.push_back(c);
2213  }
2214  }
2215  return escaped;
2216 }
2217 
2219 {
2220  return preferences_[prefs_list::remember_password].to_bool();
2221 }
2222 
2223 void prefs::set_remember_password(bool remember)
2224 {
2225  preferences_[prefs_list::remember_password] = remember;
2226 
2227  if(remember) {
2228  load_credentials();
2229  } else {
2231  }
2232 }
2233 
2234 std::string prefs::login()
2235 {
2236  std::string name = get("login", pref_constants::EMPTY_LOGIN);
2237  if(name == pref_constants::EMPTY_LOGIN) {
2238  name = get_system_username();
2239  } else if(name.size() > 2 && name.front() == '@' && name.back() == '@') {
2240  name = name.substr(1, name.size() - 2);
2241  } else {
2242  ERR_CFG << "malformed user credentials (did you manually edit the preferences file?)";
2243  }
2244  if(name.empty()) {
2245  return "player";
2246  }
2247  return name;
2248 }
2249 
2250 void prefs::set_login(const std::string& login)
2251 {
2252  auto login_clean = login;
2253  boost::trim(login_clean);
2254 
2255  preferences_[prefs_list::login] = '@' + login_clean + '@';
2256 }
2257 
2258 std::string prefs::password(const std::string& server, const std::string& login)
2259 {
2260  DBG_CFG << "Retrieving password for server: '" << server << "', login: '" << login << "'";
2261  auto login_clean = login;
2262  boost::trim(login_clean);
2263 
2264  if(!remember_password()) {
2265  if(!credentials_.empty() && credentials_[0].username == login_clean && credentials_[0].server == server) {
2266  auto temp = aes_decrypt(credentials_[0].key, build_key(server, login_clean));
2267  return std::string(temp.begin(), temp.end());
2268  } else {
2269  return "";
2270  }
2271  }
2272  auto cred = std::find_if(credentials_.begin(), credentials_.end(), [&](const preferences::login_info& cred) {
2273  return cred.server == server && cred.username == login_clean;
2274  });
2275  if(cred == credentials_.end()) {
2276  return "";
2277  }
2278  auto temp = aes_decrypt(cred->key, build_key(server, login_clean));
2279  return std::string(temp.begin(), temp.end());
2280 }
2281 
2282 void prefs::set_password(const std::string& server, const std::string& login, const std::string& key)
2283 {
2284  DBG_CFG << "Setting password for server: '" << server << "', login: '" << login << "'";
2285  auto login_clean = login;
2286  boost::trim(login_clean);
2287 
2288  preferences::secure_buffer temp(key.begin(), key.end());
2289  if(!remember_password()) {
2291  credentials_.emplace_back(login_clean, server, aes_encrypt(temp, build_key(server, login_clean)));
2292  return;
2293  }
2294  auto cred = std::find_if(credentials_.begin(), credentials_.end(), [&](const preferences::login_info& cred) {
2295  return cred.server == server && cred.username == login_clean;
2296  });
2297  if(cred == credentials_.end()) {
2298  // This is equivalent to emplace_back, but also returns the iterator to the new element
2299  cred = credentials_.emplace(credentials_.end(), login_clean, server);
2300  }
2301  cred->key = aes_encrypt(temp, build_key(server, login_clean));
2302 }
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
void remove_attribute(std::string_view key)
Definition: config.cpp:162
config & add_child(std::string_view key)
Definition: config.cpp:436
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:188
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
const_attr_itors attribute_range() const
Definition: config.cpp:740
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
Definition: config.cpp:401
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:280
child_itors child_range(std::string_view key)
Definition: config.cpp:268
void clear_children(T... keys)
Definition: config.hpp:601
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1097
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
bool has_attribute(std::string_view key) const
Definition: config.cpp:157
bool empty() const
Definition: config.cpp:823
void clear()
Definition: config.cpp:802
void remove_children(std::string_view key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:634
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
void set_theme(const std::string &new_theme)
Definition: display.cpp:241
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
Game board class.
Definition: game_board.hpp:47
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
A class grating read only view to a vector of config objects, viewed as one config with all children ...
config_array_view child_range(std::string_view key) const
static game_config_view wrap(const config &cfg)
void for_each_loc(const F &f) const
Definition: map.hpp:140
Encapsulates the map of the game.
Definition: map.hpp:176
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:78
file_dialog & set_ok_label(const std::string &value)
Sets the OK button label.
file_dialog & set_path(const std::string &value)
Sets the initial file selection.
file_dialog & set_title(const std::string &value)
Sets the current dialog title text.
Definition: file_dialog.hpp:59
file_dialog & set_read_only(bool value)
Whether to provide user interface elements for manipulating existing objects.
file_dialog & set_filename(const std::string &value)
Sets the initial file name input but not the path.
file_dialog & set_message(const std::string &value)
Sets the current dialog instructions/message text.
Definition: file_dialog.hpp:78
bool show(const unsigned auto_close_time=0)
Shows the window.
int selected_index() const
Returns the selected item index after displaying.
Definition: theme_list.hpp:35
void set_selected_index(int index)
Sets the initially selected item index (-1 by default).
Definition: theme_list.hpp:41
const std::string & get_nick() const
Definition: preferences.hpp:93
void set_lobby_joins(pref_constants::lobby_joins show)
std::string enemy_color()
config::attribute_value get_as_attribute(const std::string &key)
void set_reach_map_enemy_color(const std::string &color_id)
void set_network_host(const std::string &host)
std::set< t_translation::terrain_code > & encountered_terrains()
const config & options()
bool set_music(bool ison)
void set_remember_password(bool remember)
const std::map< std::string, preferences::acquaintance > & get_acquaintances()
optional_const_config get_alias()
bool show_theme_dialog()
void set_turbo(bool ison)
std::string get_system_username()
sound::volume music_volume()
void set_sound_volume(sound::volume vol)
bool mp_modifications_initialized_
std::map< std::string, std::vector< std::string > > history_map_
void save_hotkeys()
int village_support()
void set_addon_manager_saved_order_direction(sort_order::type value)
std::chrono::seconds countdown_turn_bonus()
bool middle_click_scrolls()
bool turn_bell()
void set_countdown_reservoir_time(const std::chrono::seconds &value)
void remove_game_preset(int id)
std::set< std::string > & encountered_units()
void add_alias(const std::string &alias, const std::string &command)
void write_preferences()
void clear_preferences()
std::vector< std::string > sp_modifications_
void clear_mp_alert_prefs()
The most recently selected add-on id from the editor.
void set_village_gold(int value)
bool confirm_load_save_from_different_version()
std::string network_host()
void clear_countdown_init_time()
std::set< std::string > unknown_unsynced_attributes_
sound::volume sound_volume()
bool set_ui_sound(bool ison)
void set_reach_map_border_opacity(const int new_opacity)
std::chrono::minutes chat_message_aging()
void set_login(const std::string &login)
static prefs & get()
const std::string get_ignored_delim()
std::map< std::string, preferences::acquaintance > acquaintances_
static constexpr std::array unsynced_children_
sound::volume bell_volume()
bool fps_
config::child_itors get_game_presets()
int scroll_speed()
bool message_private()
void encounter_recallable_units(const std::vector< team > &teams)
std::map< std::string, std::set< std::string > > completed_campaigns_
bool show_combat()
std::string theme()
optional_const_config dir_bookmarks()
void set_theme(const std::string &theme)
void set_xp_modifier(int value)
void set_user_servers_list(const std::vector< game_config::server_info > &value)
preferences::secure_buffer build_key(const std::string &server, const std::string &login)
Fills a secure_buffer with 32 bytes of deterministically generated bytes, then overwrites it with the...
bool sound()
bool is_campaign_completed(const std::string &campaign_id)
void set_allied_color(const std::string &color_id)
bool get_scroll_when_mouse_outside(bool def)
void show_wesnothd_server_search()
bool green_confirm()
int mouse_scroll_threshold()
Gets the threshold for when to scroll.
std::string get_chat_timestamp(const std::chrono::system_clock::time_point &t)
bool is_ignored(const std::string &nick)
std::vector< std::string > mp_modifications_
bool achievement(const std::string &content_for, const std::string &id)
std::set< std::string > unknown_synced_attributes_
void set_enemy_color(const std::string &color_id)
void save_credentials()
std::chrono::seconds countdown_init_time()
void set_password(const std::string &server, const std::string &login, const std::string &key)
bool sub_achievement(const std::string &content_for, const std::string &id, const std::string &sub_id)
void clear_credentials()
std::vector< preferences::login_info > credentials_
std::vector< std::string > do_read_editor_mru()
void encounter_recruitable_units(const std::vector< team > &teams)
bool parse_should_show_lobby_join(const std::string &sender, const std::string &message)
int progress_achievement(const std::string &content_for, const std::string &id, int limit=999999, int max_progress=999999, int amount=0)
Increments the achievement's current progress by amount if it hasn't already been completed.
sort_order::type addon_manager_saved_order_direction()
bool set_turn_bell(bool ison)
static constexpr std::array synced_attributes_
unsigned int sample_rate()
void set_reach_map_tint_opacity(const int new_opacity)
sound::volume ui_volume()
void set_countdown_turn_bonus(const std::chrono::seconds &value)
static constexpr std::array synced_children_
void set_options(const config &values)
int reach_map_tint_opacity()
void set_message_private(bool value)
std::vector< game_config::server_info > user_servers_list()
optional_const_config get_child(const std::string &key)
void load_credentials()
std::string unmoved_color()
void load_advanced_prefs(const game_config_view &gc)
std::string allied_color()
void set_show_standing_animations(bool value)
void set_show_fps(bool value)
void set_pixel_scale(const int scale)
std::size_t editor_mru_limit()
void set_color_cursors(bool value)
std::vector< preferences::option > advanced_prefs_
void encounter_start_units(const unit_map &units)
pref_constants::lobby_joins get_lobby_joins()
bool show_fps()
void load_hotkeys()
void set_chat_message_aging(const std::chrono::minutes &value)
optional_const_config get_game_preset(int id)
void encounter_map_terrain(const gamemap &map)
void set_campaign_rng_mode_default_for_migration()
int font_scaling()
void set_village_support(int value)
preferences::secure_buffer escape(const preferences::secure_buffer &text)
std::chrono::seconds countdown_action_bonus()
std::string moved_color()
bool confirm_no_moves()
void set_font_scaling(int scale)
std::size_t sound_buffer_size()
bool use_color_cursors()
bool yellow_confirm()
config option_values_
void migrate_preferences(const std::string &prefs_dir)
bool message_bell()
void set_dir_bookmarks(const config &cfg)
bool message_private_on_
std::set< t_translation::terrain_code > encountered_terrains_set_
bool set_sound(bool ison)
bool is_friend(const std::string &nick)
void set_achievement(const std::string &content_for, const std::string &id)
Marks the specified achievement as completed.
preferences::secure_buffer aes_decrypt(const preferences::secure_buffer &text, const preferences::secure_buffer &key)
Same as aes_encrypt(), except of course it takes encrypted data as an argument and returns decrypted ...
int village_gold()
config preferences_
void clear_countdown_turn_bonus()
std::string login()
bool music_on()
int font_scaled(int size)
void set_scroll_speed(const int scroll)
void set_music_volume(sound::volume vol)
std::pair< preferences::acquaintance *, bool > add_acquaintance(const std::string &nick, const std::string &mode, const std::string &notes)
bool ui_sound_on()
bool options_initialized_
int pixel_scale()
std::string partial_color()
preferences::secure_buffer unescape(const preferences::secure_buffer &text)
void set_resolution(const point &res)
void set_campaign_server(const std::string &host)
void set_unmoved_color(const std::string &color_id)
bool remove_acquaintance(const std::string &nick)
void clear_countdown_reservoir_time()
bool turbo()
const std::vector< std::string > & modifications(bool mp=true)
void do_commit_editor_mru(const std::vector< std::string > &mru)
std::chrono::seconds countdown_reservoir_time()
std::string reach_map_color()
compression::format save_compression_format()
std::vector< std::string > recent_files()
Retrieves the list of recently opened files.
std::set< std::string > encountered_units_set_
std::string get_mp_server_program_name()
std::string reach_map_enemy_color()
void add_game_preset(config &&preset_data)
void set_partial_color(const std::string &color_id)
void set_child(const std::string &key, const config &val)
static constexpr std::array unsynced_attributes_
std::set< std::string > all_attributes()
int keepalive_timeout()
void set_mp_server_program_name(const std::string &)
void set_bell_volume(sound::volume vol)
bool remember_password()
void add_recent_files_entry(const std::string &path)
Adds an entry to the recent files list.
void set_moved_color(const std::string &color_id)
point resolution()
void save_sample_rate(const unsigned int rate)
bool use_twelve_hour_clock_format()
static bool no_preferences_save
int reach_map_border_opacity()
void set_modifications(const std::vector< std::string > &value, bool mp=true)
void set_countdown_init_time(const std::chrono::seconds &value)
void set_sub_achievement(const std::string &content_for, const std::string &id, const std::string &sub_id)
Marks the specified sub-achievement as completed.
std::set< std::string > unknown_unsynced_children_
const std::vector< game_config::server_info > & builtin_servers_list()
void set_reach_map_color(const std::string &color_id)
void reload_preferences()
std::string campaign_server()
std::set< std::string > unknown_synced_children_
void add_completed_campaign(const std::string &campaign_id, const std::string &difficulty_level)
void clear_hotkeys()
void save_sound_buffer_size(const std::size_t size)
int xp_modifier()
void encounter_all_content(const game_board &gb)
std::string password(const std::string &server, const std::string &login)
bool sp_modifications_initialized_
std::vector< std::string > * get_history(const std::string &id)
Returns a pointer to the history vector associated with given id making a new one if it doesn't exist...
preferences::secure_buffer aes_encrypt(const preferences::secure_buffer &text, const preferences::secure_buffer &key)
Encrypts the value of text using key and a hard coded IV using AES.
void set_countdown_action_bonus(const std::chrono::seconds &value)
bool auto_open_whisper_windows()
void load_preferences()
std::map< std::string, std::string > get_acquaintances_nice(const std::string &filter)
bool get_show_deprecation(bool def)
bool show_standing_animations()
void clear_countdown_action_bonus()
void set_ui_volume(sound::volume vol)
constexpr float as_percent() const
Definition: sound.hpp:120
constexpr static volume from_percent(float percentage)
Definition: sound.hpp:117
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
const t_translation::ter_list & union_type() const
Definition: terrain.hpp:87
t_translation::terrain_code number() const
Definition: terrain.hpp:66
Definition: theme.hpp:43
static std::vector< theme_info > get_basic_theme_info(bool include_hidden=false)
Returns minimal info about saved themes, optionally including hidden ones.
Definition: theme.cpp:1001
Container associating units to locations.
Definition: map.hpp:98
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1031
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).
General settings and defaults for scenarios.
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
Definition: chrono.hpp:79
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition: chrono.hpp:70
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:171
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:228
void fill(const ::rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:62
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:203
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::chrono::system_clock::time_point file_modified_time(const bfs::path &path)
void copy_file(const std::string &src, const std::string &dest)
Read a file and then writes it back out.
bool delete_file(const std::string &filename)
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:344
std::string get_exe_dir()
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::string get_synced_prefs_file()
location of preferences file containing preferences that are synced between computers note that wesno...
std::string get_unsynced_prefs_file()
location of preferences file containing preferences that aren't synced between computers
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:52
std::string get_credentials_file()
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:53
std::string get_wesnothd_name()
std::string get_default_prefs_file()
std::string partial_orb_color
std::string reach_map_enemy_color
std::string moved_orb_color
std::string unmoved_orb_color
std::string ally_orb_color
std::string enemy_orb_color
std::string reach_map_color
std::string turn_bell
std::string path
Definition: filesystem.cpp:106
const version_info wesnoth_version(VERSION)
int reach_map_border_opacity
std::vector< server_info > server_list
Definition: game_config.cpp:76
int reach_map_tint_opacity
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void save_hotkeys(config &cfg)
Save the non-default hotkeys to the config.
void reset_default_hotkeys()
Reset all hotkeys to the defaults.
void load_custom_hotkeys(const game_config_view &cfg)
Registers all hotkeys present in this config, overwriting any matching default hotkeys.
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
void write(std::ostream &out, const configr_of &cfg, unsigned int level, bool strong_quotes)
Definition: parser.cpp:748
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:49
const int min_window_height
Definition: preferences.hpp:38
const int max_pixel_scale
Definition: preferences.hpp:50
const std::string EMPTY_LOGIN
Definition: preferences.hpp:58
const int min_pixel_scale
Definition: preferences.hpp:49
const std::string default_addons_server
Definition: preferences.hpp:62
const int def_window_width
Definition: preferences.hpp:40
const int min_font_scaling
Definition: preferences.hpp:46
const int min_window_width
Definition: preferences.hpp:37
const int max_font_scaling
Definition: preferences.hpp:47
const int def_window_height
Definition: preferences.hpp:41
const unsigned char CREDENTIAL_SEPARATOR
Definition: preferences.hpp:57
game_data * gamedata
Definition: resources.cpp:22
static std::string at(const std::string &file, int line)
int get_village_support(const std::string &value)
Gets the village unit level support.
int get_xp_modifier(const std::string &value)
Gets the xp modifier.
int get_village_gold(const std::string &value, const game_classification *classification)
Gets the village gold.
void set_UI_volume(volume vol)
Definition: sound.cpp:1029
void reset_sound()
Definition: sound.cpp:443
void set_music_volume(volume vol)
Definition: sound.cpp:983
bool init_sound()
Definition: sound.cpp:346
void close_sound()
Definition: sound.cpp:413
void play_music()
Definition: sound.cpp:523
void stop_music()
Definition: sound.cpp:472
void set_sound_volume(volume vol)
Definition: sound.cpp:1000
void stop_UI_sound()
Definition: sound.cpp:500
void set_bell_volume(volume vol)
Definition: sound.cpp:1019
void stop_bell()
Definition: sound.cpp:492
void stop_sound()
Definition: sound.cpp:481
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
ter_list read_list(std::string_view str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
std::string write_list(const ter_list &list)
Writes a list of terrains to a string, only writes the new format.
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
constexpr auto values
Definition: ranges.hpp:46
constexpr auto filter
Definition: ranges.hpp:42
@ REMOVE_EMPTY
void trim(std::string_view &s)
bool isvalid_wildcard(const std::string &username)
Check if the username pattern contains only valid characters.
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition: general.hpp:118
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
Definition: general.hpp:151
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
bool headless()
The game is running headless.
Definition: video.cpp:146
std::string to_string(const Range &range, const Func &op)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
bool show(std::string title, std::string message)
Displays a tray notification.
void scale(size_t factor, const uint32_t *src, uint32_t *trg, int srcWidth, int srcHeight, ColorFormat colFmt, const ScalerCfg &cfg=ScalerCfg(), int yFirst=0, int yLast=std::numeric_limits< int >::max())
Definition: xbrz.cpp:1175
std::string_view data
Definition: picture.cpp:188
static lg::log_domain log_filesystem("filesystem")
#define ERR_CFG
Definition: preferences.cpp:61
#define DBG_CFG
Definition: preferences.cpp:62
#define ERR_ADV
Definition: preferences.cpp:68
static lg::log_domain advanced_preferences("advanced_preferences")
static std::string fix_orb_color_name(const std::string &color)
#define ERR_FS
Definition: preferences.cpp:65
static lg::log_domain log_config("config")
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::string filename
Filename.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:56
Encapsulates the map of the game.
Definition: location.hpp:46
Holds a 2D point.
Definition: point.hpp:25
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
mock_char c
static map_location::direction n
static map_location::direction s
#define d
#define e