The Battle for Wesnoth  1.19.25+dev
game_config.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "game_config.hpp"
17 
18 #include "color_range.hpp"
19 #include "config.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 #include "game_version.hpp"
23 #include "serialization/chrono.hpp"
25 
26 #include <cmath>
27 #include <random>
28 
29 static lg::log_domain log_engine("engine");
30 #define LOG_NG LOG_STREAM(info, log_engine)
31 #define ERR_NG LOG_STREAM(err, log_engine)
32 
33 using namespace std::chrono_literals;
34 
35 namespace game_config
36 {
37 //
38 // Gameplay constants
39 //
40 int base_income = 2;
43 int recall_cost = 20;
46 
47 int poison_amount = 8;
49 
51 
52 //
53 // Terrain-related constants
54 //
55 unsigned int tile_size = 72;
56 
57 std::string default_terrain;
59 
60 std::vector<unsigned int> zoom_levels {36, 72, 144};
61 
62 //
63 // Display scale constants
64 //
65 double hp_bar_scaling = 0.666;
66 double xp_bar_scaling = 0.5;
67 
68 //
69 // Misc
70 //
71 std::chrono::milliseconds lobby_network_timer = 100ms;
72 std::chrono::milliseconds lobby_refresh = 4000ms;
73 
74 const std::size_t max_loop = 65536;
75 
76 std::vector<server_info> server_list;
77 
78 bool allow_insecure = false;
79 bool addon_server_info = false;
80 bool no_simd = false;
81 
82 //
83 // Gamestate flags
84 //
85 bool
86  debug_impl = false,
87  debug_lua = false,
88  strict_lua = false,
89  editor = false,
91  mp_debug = false,
92  exit_at_end = false,
94  no_addons = false;
95 
96 const bool& debug = debug_impl;
97 
98 void set_debug(bool new_debug) {
99  // TODO: remove severity static casts and fix issue #7894
100  if(debug_impl && !new_debug) {
101  // Turning debug mode off; decrease deprecation severity
103  if(lg::get_log_domain_severity("deprecation", severity)) {
104  int severityInt = static_cast<int>(severity);
105  lg::set_log_domain_severity("deprecation", static_cast<lg::severity>(severityInt - 2));
106  }
107  } else if(!debug_impl && new_debug) {
108  // Turning debug mode on; increase deprecation severity
110  if(lg::get_log_domain_severity("deprecation", severity)) {
111  int severityInt = static_cast<int>(severity);
112  lg::set_log_domain_severity("deprecation", static_cast<lg::severity>(severityInt + 2));
113  }
114  }
115  debug_impl = new_debug;
116 }
117 
118 //
119 // Orb display flags
120 //
128 
129 //
130 // Reach map opacity variables
131 //
132 
135 
136 //
137 // Music constants
138 //
140 
141 std::vector<std::string> default_defeat_music;
142 std::vector<std::string> default_victory_music;
143 
144 //
145 // Color info
146 //
147 std::string flag_rgb, unit_rgb;
148 
149 std::vector<color_t> red_green_scale;
150 std::vector<color_t> red_green_scale_text;
151 
152 std::vector<color_t> blue_white_scale;
153 std::vector<color_t> blue_white_scale_text;
154 
155 std::map<std::string, color_range, std::less<>> team_rgb_range;
156 // Map [color_range]id to [color_range]name, or "" if no name
157 std::map<std::string, t_string, std::less<>> team_rgb_name;
158 
159 std::map<std::string, std::vector<color_t>, std::less<>> team_rgb_colors;
160 
161 std::vector<std::string> default_colors;
162 
163 namespace colors
164 {
165 std::string ally_orb_color;
166 std::string enemy_orb_color;
167 std::string moved_orb_color;
168 std::string partial_orb_color;
169 std::string unmoved_orb_color;
170 std::string reach_map_color;
172 std::string default_color_list;
173 } // namespace colors
174 
175 //
176 // Image constants
177 //
178 std::vector<std::string> foot_speed_prefix;
179 
181 std::string foot_teleport_exit;
182 
183 namespace images {
184 
185 std::string
193  // orbs and hp/xp bar
197  // top bar icons
200  // flags
203  // hex overlay
211  // GUI elements
215  // TODO: de-hardcode this
216  wml_menu = "buttons/WML-custom.png",
221  // notifications icon
222  app_icon = "images/icons/icon-game.png";
223 
224 } //images
225 
226 //
227 // Sound constants
228 //
229 namespace sounds {
230 
231 std::string
232  turn_bell = "bell.wav",
233  timer_bell = "timer.wav",
234  public_message = "chat-[1~3].ogg",
235  private_message = "chat-highlight.ogg",
236  friend_message = "chat-friend.ogg",
237  server_message = "receive.wav",
238  player_joins = "arrive.wav",
239  player_leaves = "leave.wav",
240  game_user_arrive = "join.wav",
241  game_user_leave = "leave.wav",
242  ready_for_start = "bell.wav",
243  game_has_begun = "gamestart.ogg",
244  game_created = "join.wav";
245 
246 const std::string
247  button_press = "button.wav",
248  checkbox_release = "checkbox.wav",
249  slider_adjust = "slider.wav",
250  menu_expand = "expand.wav",
251  menu_contract = "contract.wav",
252  menu_select = "select.wav";
253 
254 namespace status {
255 
256 std::string
257  poisoned = "poison.ogg",
258  slowed = "slowed.wav",
259  petrified = "petrified.ogg";
260 
261 } // status
262 
263 } // sounds
264 
265 static void add_color_info(const game_config_view& v, bool build_defaults);
267 {
268  add_color_info(v, false);
269 }
270 
271 void load_config(const config &v)
272 {
273  base_income = v["base_income"].to_int(2);
274  village_income = v["village_income"].to_int(1);
275  village_support = v["village_support"].to_int(1);
276  poison_amount = v["poison_amount"].to_int(8);
277  rest_heal_amount = v["rest_heal_amount"].to_int(2);
278  recall_cost = v["recall_cost"].to_int(20);
279  kill_experience = v["kill_experience"].to_int(8);
280  combat_experience= v["combat_experience"].to_int(1);
281  lobby_refresh = chrono::parse_duration(v["lobby_refresh"], 2000ms);
282  default_terrain = v["default_terrain"].str();
283  tile_size = v["tile_size"].to_int(72);
284 
285  std::vector<std::string> zoom_levels_str = utils::split(v["zoom_levels"]);
286  if(!zoom_levels_str.empty()) {
287  zoom_levels.clear();
288  std::transform(zoom_levels_str.begin(), zoom_levels_str.end(), std::back_inserter(zoom_levels), [](const std::string& zoom) {
289  int z = std::stoi(zoom);
290  if((z / 4) * 4 != z) {
291  ERR_NG << "zoom level " << z << " is not divisible by 4."
292  << " This will cause graphical glitches!";
293  }
294  return z;
295  });
296  }
297 
298  title_music = v["title_music"].str();
299  lobby_music = v["lobby_music"].str();
300 
301  default_victory_music = utils::split(v["default_victory_music"].str());
302  default_defeat_music = utils::split(v["default_defeat_music"].str());
303 
304  if(auto i = v.optional_child("colors")){
305  using namespace game_config::colors;
306 
307  moved_orb_color = i["moved_orb_color"].str();
308  unmoved_orb_color = i["unmoved_orb_color"].str();
309  partial_orb_color = i["partial_orb_color"].str();
310  enemy_orb_color = i["enemy_orb_color"].str();
311  ally_orb_color = i["ally_orb_color"].str();
312  reach_map_color = i["reach_map_color"].str();
313  reach_map_enemy_color = i["reach_map_enemy_color"].str();
314  } // colors
315 
316  show_ally_orb = v["show_ally_orb"].to_bool(true);
317  show_enemy_orb = v["show_enemy_orb"].to_bool(false);
318  show_moved_orb = v["show_moved_orb"].to_bool(true);
319  show_partial_orb = v["show_partial_orb"].to_bool(true);
320  show_status_on_ally_orb = v["show_status_on_ally_orb"].to_bool(true);
321  show_unmoved_orb = v["show_unmoved_orb"].to_bool(true);
322  show_disengaged_orb = v["show_disengaged_orb"].to_bool(true);
323 
324  if(auto i = v.optional_child("images")){
325  using namespace game_config::images;
326 
327  if (!i["game_title_background"].blank()) {
328  // Select a background at random
329  const auto backgrounds = utils::split(i["game_title_background"].str());
330  if (backgrounds.size() > 1) {
331  int r = rand() % (backgrounds.size());
332  game_title_background = backgrounds.at(r);
333  } else if (backgrounds.size() == 1) {
334  game_title_background = backgrounds.at(0);
335  }
336  }
337 
338  // Allow game_title to be empty
339  game_title = i["game_title"].str();
340  game_logo = i["game_logo"].str();
341  game_logo_background = i["game_logo_background"].str();
342 
343  victory_laurel = i["victory_laurel"].str();
344  victory_laurel_hardest = i["victory_laurel_hardest"].str();
345  victory_laurel_easy = i["victory_laurel_easy"].str();
346 
347  orb = i["orb"].str();
348  orb_two_color = i["orb_two_color"].str();
349  energy = i["energy"].str();
350 
351  battery_icon = i["battery_icon"].str();
352  time_icon = i["time_icon"].str();
353 
354  flag = i["flag"].str();
355  flag_icon = i["flag_icon"].str();
356 
357  terrain_mask = i["terrain_mask"].str();
358  grid_top = i["grid_top"].str();
359  grid_bottom = i["grid_bottom"].str();
360  mouseover = i["mouseover"].str();
361  selected = i["selected"].str();
362  editor_brush = i["editor_brush"].str();
363  linger = i["linger"].str();
364 
365  observer = i["observer"].str();
366  tod_bright = i["tod_bright"].str();
367  tod_dark = i["tod_dark"].str();
368  level = i["level"].str();
369  ellipsis = i["ellipsis"].str();
370  missing = i["missing"].str();
371  blank = i["blank"].str();
372  } // images
373 
374  hp_bar_scaling = v["hp_bar_scaling"].to_double(0.666);
375  xp_bar_scaling = v["xp_bar_scaling"].to_double(0.5);
376 
377  foot_speed_prefix = utils::split(v["footprint_prefix"]);
378  foot_teleport_enter = v["footprint_teleport_enter"].str();
379  foot_teleport_exit = v["footprint_teleport_exit"].str();
380 
381  shroud_prefix = v["shroud_prefix"].str();
382  fog_prefix = v["fog_prefix"].str();
383  reach_map_prefix = v["reach_map_prefix"].str();
384  reach_map_border_opacity = v["reach_map_border_opacity"].to_int(100);
385  reach_map_tint_opacity = v["reach_map_tint_opacity"].to_int(50);//tint is at 50% by default instead of 100% to allow players to make it more opaque than normal
386 
388 
389  if(const config::attribute_value* a = v.get("flag_rgb")) {
390  flag_rgb = a->str();
391  }
392 
393  if(const config::attribute_value* a = v.get("unit_rgb")) {
394  unit_rgb = a->str();
395  }
396 
397  const auto parse_config_color_list = [&](
398  const std::string& key,
399  const color_t fallback)->std::vector<color_t>
400  {
401  std::vector<color_t> color_vec;
402 
403  for(const auto& s : utils::split(v[key].str())) {
404  try {
405  color_vec.push_back(color_t::from_hex_string(s));
406  } catch(const std::invalid_argument& e) {
407  ERR_NG << "Error parsing color list '" << key << "'.\n" << e.what();
408  color_vec.push_back(fallback);
409  }
410  }
411 
412  return color_vec;
413  };
414 
415  red_green_scale = parse_config_color_list("red_green_scale", {255, 255, 255});
416  red_green_scale_text = parse_config_color_list("red_green_scale_text", {255, 255, 255});
417  blue_white_scale = parse_config_color_list("blue_white_scale", {0 , 0 , 255});
418  blue_white_scale_text = parse_config_color_list("blue_white_scale_text", {0 , 0 , 255});
419 
420  server_list.clear();
421 
422  for(const config& server : v.child_range("server")) {
423  server_info sinf;
424  sinf.name = server["name"].str();
425  sinf.address = server["address"].str();
426  server_list.push_back(sinf);
427  }
428 
429  if(auto s = v.optional_child("sounds")) {
430  using namespace game_config::sounds;
431 
432  const auto load_attribute = [](const config& c, const std::string& key, std::string& member) {
433  if(c.has_attribute(key)) {
434  member = c[key].str();
435  }
436  };
437 
438  load_attribute(*s, "turn_bell", turn_bell);
439  load_attribute(*s, "timer_bell", timer_bell);
440  load_attribute(*s, "public_message", public_message);
441  load_attribute(*s, "private_message", private_message);
442  load_attribute(*s, "friend_message", friend_message);
443  load_attribute(*s, "server_message", server_message);
444  load_attribute(*s, "player_joins", player_joins);
445  load_attribute(*s, "player_leaves", player_leaves);
446  load_attribute(*s, "game_created", game_created);
447  load_attribute(*s, "game_user_arrive", game_user_arrive);
448  load_attribute(*s, "game_user_leave", game_user_leave);
449  load_attribute(*s, "ready_for_start", ready_for_start);
450  load_attribute(*s, "game_has_begun", game_has_begun);
451 
452  if(auto ss = s->optional_child("status")) {
453  using namespace game_config::sounds::status;
454 
455  load_attribute(*ss, "poisoned", poisoned);
456  load_attribute(*ss, "slowed", slowed);
457  load_attribute(*ss, "petrified", petrified);
458  }
459  }
460 }
461 
462 void add_color_info(const game_config_view& v, bool build_defaults)
463 {
464  if(build_defaults) {
465  default_colors.clear();
466  }
467 
468  for(const config& teamC : v.child_range("color_range")) {
469  const config::attribute_value* a1 = teamC.get("id"), *a2 = teamC.get("rgb");
470  if(!a1 || !a2) {
471  continue;
472  }
473 
474  std::string id = *a1;
475  std::vector<color_t> temp;
476 
477  for(const auto& s : utils::split(*a2)) {
478  try {
479  temp.push_back(color_t::from_hex_string(s));
480  } catch(const std::invalid_argument&) {
481  std::stringstream ss;
482  ss << "can't parse color string:\n" << teamC.debug() << "\n";
483  throw config::error(ss.str());
484  }
485  }
486 
487  team_rgb_range.emplace(id, color_range(temp));
488  team_rgb_name.emplace(id, teamC["name"].t_str());
489 
490  LOG_NG << "registered color range '" << id << "': " << team_rgb_range[id].debug();
491 
492  // Generate palette of same name;
494 
495  if(build_defaults && teamC["default"].to_bool()) {
496  default_colors.push_back(*a1);
497  }
498  }
499 
500  for(const config &cp : v.child_range("color_palette")) {
501  for(const auto& [key, value] : cp.attribute_range()) {
502  std::vector<color_t> temp;
503  for(const auto& s : utils::split(value)) {
504  try {
505  temp.push_back(color_t::from_hex_string(s));
506  } catch(const std::invalid_argument& e) {
507  ERR_NG << "Invalid color in palette: " << s << " (" << e.what() << ")";
508  }
509  }
510 
511  team_rgb_colors.emplace(key, temp);
512  LOG_NG << "registered color palette: " << key;
513  }
514  }
515 }
516 
518 {
519  default_colors.clear();
520  team_rgb_colors.clear();
521  team_rgb_name.clear();
522  team_rgb_range.clear();
523 }
524 
525 const color_range& color_info(std::string_view name)
526 {
527  auto i = team_rgb_range.find(name);
528  if(i != team_rgb_range.end()) {
529  return i->second;
530  }
531 
532  std::vector<color_t> temp;
533  for(const auto& s : utils::split(name)) {
534  try {
535  temp.push_back(color_t::from_hex_string(s));
536  } catch(const std::invalid_argument&) {
537  throw config::error(_("Invalid color in range: ") + s);
538  }
539  }
540 
541  team_rgb_range.emplace(name, color_range(temp));
542  return color_info(name);
543 }
544 
545 const std::vector<color_t>& tc_info(std::string_view name)
546 {
547  auto i = team_rgb_colors.find(name);
548  if(i != team_rgb_colors.end()) {
549  return i->second;
550  }
551 
552  std::vector<color_t> temp;
553  for(const auto& s : utils::split(name)) {
554  try {
555  temp.push_back(color_t::from_hex_string(s));
556  } catch(const std::invalid_argument& e) {
557  static std::vector<color_t> stv;
558  ERR_NG << "Invalid color in palette: " << s << " (" << e.what() << ")";
559  return stv;
560  }
561  }
562 
563  team_rgb_colors.emplace(name, temp);
564  return tc_info(name);
565 }
566 
567 color_t red_to_green(double val, bool for_text)
568 {
569  const std::vector<color_t>& color_scale = for_text ? red_green_scale_text : red_green_scale;
570 
571  const double val_scaled = std::clamp(0.01 * val, 0.0, 1.0);
572  const int lvl = int(std::nearbyint((color_scale.size() - 1) * val_scaled));
573 
574  return color_scale[lvl];
575 }
576 
577 color_t blue_to_white(double val, bool for_text)
578 {
579  const std::vector<color_t>& color_scale = for_text ? blue_white_scale_text : blue_white_scale;
580 
581  const double val_scaled = std::clamp(0.01 * val, 0.0, 1.0);
582  const int lvl = int(std::nearbyint((color_scale.size() - 1) * val_scaled));
583 
584  return color_scale[lvl];
585 }
586 
588 {
589  std::string ret = _("The Battle for Wesnoth") + " - " + revision;
590  return ret;
591 }
592 
593 } // game_config
#define debug(x)
A color range definition is made of four reference RGB colors, used for calculating conversions from ...
Definition: color_range.hpp:43
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
child_itors child_range(std::string_view key)
Definition: config.cpp:268
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)
std::vector< color_t > generate_reference_palette(const color_range &cr)
Creates a reference color palette from a color range.
Definitions for the interface to Wesnoth Markup Language (WML).
#define zoom_levels
Definition: display.cpp:82
@ grid_bottom
Bottom half part of grid image.
@ grid_top
Top half part of grid image.
std::size_t i
Definition: function.cpp:1031
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: game_config.cpp:31
#define LOG_NG
Definition: game_config.cpp:30
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:100
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
Standard logging facilities (interface).
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
Definition: chrono.hpp:79
Manage the empty-palette in the editor.
Definition: action.cpp:31
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 default_color_list
std::string reach_map_color
std::string terrain_mask
std::string victory_laurel_hardest
std::string victory_laurel
std::string orb_two_color
std::string tod_bright
std::string time_icon
std::string selected
std::string app_icon
std::string observer
std::string ellipsis
std::string game_title_background
std::string game_title
std::string game_logo
std::string game_logo_background
std::string wml_menu
std::string flag_icon
std::string editor_brush
std::string mouseover
std::string battery_icon
std::string victory_laurel_easy
std::string tod_dark
std::string public_message
std::string private_message
const std::string menu_expand
std::string player_leaves
std::string server_message
std::string game_user_arrive
std::string turn_bell
const std::string menu_contract
std::string game_user_leave
const std::string checkbox_release
std::string timer_bell
std::string friend_message
const std::string menu_select
std::string ready_for_start
std::string game_has_begun
const std::string button_press
std::string game_created
std::string player_joins
const std::string slider_adjust
Game configuration data as global variables.
Definition: build_info.cpp:68
int rest_heal_amount
Definition: game_config.cpp:48
std::vector< std::string > default_defeat_music
std::map< std::string, color_range, std::less<> > team_rgb_range
Colors defined by WML [color_range] tags.
std::string flag_rgb
color_t blue_to_white(double val, bool for_text)
std::map< std::string, t_string, std::less<> > team_rgb_name
const std::size_t max_loop
The maximum number of hexes on a map and items in an array and also used as maximum in wml loops.
Definition: game_config.cpp:74
std::string get_default_title_string()
bool ignore_replay_errors
Definition: game_config.cpp:90
std::string foot_teleport_enter
int village_income
Definition: game_config.cpp:41
bool show_status_on_ally_orb
std::vector< color_t > red_green_scale_text
bool addon_server_info
Definition: game_config.cpp:79
bool show_moved_orb
std::vector< std::string > foot_speed_prefix
const int gold_carryover_percentage
Default percentage gold carried over to the next scenario.
Definition: game_config.cpp:50
std::string foot_teleport_exit
std::string lobby_music
const std::vector< color_t > & tc_info(std::string_view name)
std::string fog_prefix
Definition: game_config.cpp:58
const color_range & color_info(std::string_view name)
std::vector< std::string > default_colors
std::vector< color_t > blue_white_scale
std::string shroud_prefix
Definition: game_config.cpp:58
int reach_map_border_opacity
double hp_bar_scaling
Definition: game_config.cpp:65
bool disable_autosave
Definition: game_config.cpp:93
std::vector< server_info > server_list
Definition: game_config.cpp:76
int kill_experience
Definition: game_config.cpp:44
std::string unit_rgb
unsigned int tile_size
Definition: game_config.cpp:55
bool show_ally_orb
bool allow_insecure
Definition: game_config.cpp:78
bool show_disengaged_orb
void add_color_info(const game_config_view &v)
bool show_partial_orb
std::chrono::milliseconds lobby_refresh
Definition: game_config.cpp:72
std::string title_music
std::chrono::milliseconds lobby_network_timer
Definition: game_config.cpp:71
std::string default_terrain
Definition: game_config.cpp:57
bool show_enemy_orb
bool show_unmoved_orb
std::string reach_map_prefix
Definition: game_config.cpp:58
std::vector< std::string > default_victory_music
int combat_experience
Definition: game_config.cpp:45
void reset_color_info()
int reach_map_tint_opacity
const std::string revision
void set_debug(bool new_debug)
Definition: game_config.cpp:98
std::vector< color_t > red_green_scale
bool exit_at_end
Definition: game_config.cpp:92
std::vector< color_t > blue_white_scale_text
color_t red_to_green(double val, bool for_text)
Return a color corresponding to the value val red for val=0.0 to green for val=100....
std::map< std::string, std::vector< color_t >, std::less<> > team_rgb_colors
double xp_bar_scaling
Definition: game_config.cpp:66
int village_support
Definition: game_config.cpp:42
bool get_log_domain_severity(const std::string &name, severity &severity)
Definition: log.cpp:401
severity
Definition: log.hpp:82
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:379
constexpr auto transform
Definition: ranges.hpp:45
std::vector< std::string > split(const config_attribute_value &val)
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
static color_t from_hex_string(std::string_view c)
Creates a new color_t object from a string variable in hex format.
Definition: color.cpp:64
mock_char c
static map_location::direction s
#define e