The Battle for Wesnoth  1.19.27+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 
137 
138 //
139 // Music constants
140 //
142 
143 std::vector<std::string> default_defeat_music;
144 std::vector<std::string> default_victory_music;
145 
146 //
147 // Color info
148 //
149 std::string flag_rgb, unit_rgb;
150 
151 std::vector<color_t> red_green_scale;
152 std::vector<color_t> red_green_scale_text;
153 
154 std::vector<color_t> blue_white_scale;
155 std::vector<color_t> blue_white_scale_text;
156 
157 std::map<std::string, color_range, std::less<>> team_rgb_range;
158 // Map [color_range]id to [color_range]name, or "" if no name
159 std::map<std::string, t_string, std::less<>> team_rgb_name;
160 
161 std::map<std::string, std::vector<color_t>, std::less<>> team_rgb_colors;
162 
163 std::vector<std::string> default_colors;
164 
165 namespace colors
166 {
167 std::string ally_orb_color;
168 std::string enemy_orb_color;
169 std::string moved_orb_color;
170 std::string partial_orb_color;
171 std::string unmoved_orb_color;
172 std::string reach_map_color;
174 std::string default_color_list;
175 } // namespace colors
176 
177 //
178 // Image constants
179 //
180 
182 std::string foot_teleport_exit;
183 
184 namespace images {
185 
186 std::string
194  // orbs and hp/xp bar
198  // top bar icons
201  // flags
204  // hex overlay
213  // GUI elements
217  // TODO: de-hardcode this
218  wml_menu = "buttons/WML-custom.png",
223  // notifications icon
224  app_icon = "images/icons/icon-game.png";
225 
226 } //images
227 
228 //
229 // Sound constants
230 //
231 namespace sounds {
232 
233 std::string
234  turn_bell = "bell.wav",
235  timer_bell = "timer.wav",
236  public_message = "chat-[1~3].ogg",
237  private_message = "chat-highlight.ogg",
238  friend_message = "chat-friend.ogg",
239  server_message = "receive.wav",
240  player_joins = "arrive.wav",
241  player_leaves = "leave.wav",
242  game_user_arrive = "join.wav",
243  game_user_leave = "leave.wav",
244  ready_for_start = "bell.wav",
245  game_has_begun = "gamestart.ogg",
246  game_created = "join.wav";
247 
248 const std::string
249  button_press = "button.wav",
250  checkbox_release = "checkbox.wav",
251  slider_adjust = "slider.wav",
252  menu_expand = "expand.wav",
253  menu_contract = "contract.wav",
254  menu_select = "select.wav";
255 
256 namespace status {
257 
258 std::string
259  poisoned = "poison.ogg",
260  slowed = "slowed.wav",
261  petrified = "petrified.ogg";
262 
263 } // status
264 
265 } // sounds
266 
267 static void add_color_info(const game_config_view& v, bool build_defaults);
269 {
270  add_color_info(v, false);
271 }
272 
273 void load_config(const config &v)
274 {
275  base_income = v["base_income"].to_int(2);
276  village_income = v["village_income"].to_int(1);
277  village_support = v["village_support"].to_int(1);
278  poison_amount = v["poison_amount"].to_int(8);
279  rest_heal_amount = v["rest_heal_amount"].to_int(2);
280  recall_cost = v["recall_cost"].to_int(20);
281  kill_experience = v["kill_experience"].to_int(8);
282  combat_experience= v["combat_experience"].to_int(1);
283  lobby_refresh = chrono::parse_duration(v["lobby_refresh"], 2000ms);
284  default_terrain = v["default_terrain"].str();
285  tile_size = v["tile_size"].to_int(72);
286 
287  std::vector<std::string> zoom_levels_str = utils::split(v["zoom_levels"]);
288  if(!zoom_levels_str.empty()) {
289  zoom_levels.clear();
290  std::transform(zoom_levels_str.begin(), zoom_levels_str.end(), std::back_inserter(zoom_levels), [](const std::string& zoom) {
291  int z = std::stoi(zoom);
292  if((z / 4) * 4 != z) {
293  ERR_NG << "zoom level " << z << " is not divisible by 4."
294  << " This will cause graphical glitches!";
295  }
296  return z;
297  });
298  }
299 
300  title_music = v["title_music"].str();
301  lobby_music = v["lobby_music"].str();
302 
303  default_victory_music = utils::split(v["default_victory_music"].str());
304  default_defeat_music = utils::split(v["default_defeat_music"].str());
305 
306  if(auto i = v.optional_child("colors")){
307  using namespace game_config::colors;
308 
309  moved_orb_color = i["moved_orb_color"].str();
310  unmoved_orb_color = i["unmoved_orb_color"].str();
311  partial_orb_color = i["partial_orb_color"].str();
312  enemy_orb_color = i["enemy_orb_color"].str();
313  ally_orb_color = i["ally_orb_color"].str();
314  reach_map_color = i["reach_map_color"].str();
315  reach_map_enemy_color = i["reach_map_enemy_color"].str();
316  } // colors
317 
318  show_ally_orb = v["show_ally_orb"].to_bool(true);
319  show_enemy_orb = v["show_enemy_orb"].to_bool(false);
320  show_moved_orb = v["show_moved_orb"].to_bool(true);
321  show_partial_orb = v["show_partial_orb"].to_bool(true);
322  show_status_on_ally_orb = v["show_status_on_ally_orb"].to_bool(true);
323  show_unmoved_orb = v["show_unmoved_orb"].to_bool(true);
324  show_disengaged_orb = v["show_disengaged_orb"].to_bool(true);
325 
326  if(auto i = v.optional_child("images")){
327  using namespace game_config::images;
328 
329  if (!i["game_title_background"].blank()) {
330  // Select a background at random
331  const auto backgrounds = utils::split(i["game_title_background"].str());
332  if (backgrounds.size() > 1) {
333  int r = rand() % (backgrounds.size());
334  game_title_background = backgrounds.at(r);
335  } else if (backgrounds.size() == 1) {
336  game_title_background = backgrounds.at(0);
337  }
338  }
339 
340  // Allow game_title to be empty
341  game_title = i["game_title"].str();
342  game_logo = i["game_logo"].str();
343  game_logo_background = i["game_logo_background"].str();
344 
345  victory_laurel = i["victory_laurel"].str();
346  victory_laurel_hardest = i["victory_laurel_hardest"].str();
347  victory_laurel_easy = i["victory_laurel_easy"].str();
348 
349  orb = i["orb"].str();
350  orb_two_color = i["orb_two_color"].str();
351  energy = i["energy"].str();
352 
353  battery_icon = i["battery_icon"].str();
354  time_icon = i["time_icon"].str();
355 
356  flag = i["flag"].str();
357  flag_icon = i["flag_icon"].str();
358 
359  terrain_mask = i["terrain_mask"].str();
360  grid_top = i["grid_top"].str();
361  grid_bottom = i["grid_bottom"].str();
362  mouseover = i["mouseover"].str();
363  selected = i["selected"].str();
364  editor_brush = i["editor_brush"].str();
365  unreachable = i["unreachable"].str();
366  linger = i["linger"].str();
367 
368  observer = i["observer"].str();
369  tod_bright = i["tod_bright"].str();
370  tod_dark = i["tod_dark"].str();
371  level = i["level"].str();
372  ellipsis = i["ellipsis"].str();
373  missing = i["missing"].str();
374  blank = i["blank"].str();
375  } // images
376 
377  hp_bar_scaling = v["hp_bar_scaling"].to_double(0.666);
378  xp_bar_scaling = v["xp_bar_scaling"].to_double(0.5);
379 
380  foot_teleport_enter = v["footprint_teleport_enter"].str();
381  foot_teleport_exit = v["footprint_teleport_exit"].str();
382 
383  shroud_prefix = v["shroud_prefix"].str();
384  fog_prefix = v["fog_prefix"].str();
385  reach_map_prefix = v["reach_map_prefix"].str();
386  reach_map_border_opacity = v["reach_map_border_opacity"].to_int(60);
387  reach_map_tint_opacity = v["reach_map_tint_opacity"].to_int(30);
388  reach_map_darken_opacity = v["reach_map_darken_opacity"].to_int(60);
389  reach_map_darken_tex = v["reach_map_darken_tex"].to_bool(true);
390 
392 
393  if(const config::attribute_value* a = v.get("flag_rgb")) {
394  flag_rgb = a->str();
395  }
396 
397  if(const config::attribute_value* a = v.get("unit_rgb")) {
398  unit_rgb = a->str();
399  }
400 
401  const auto parse_config_color_list = [&](
402  const std::string& key,
403  const color_t fallback)->std::vector<color_t>
404  {
405  std::vector<color_t> color_vec;
406 
407  for(const auto& s : utils::split(v[key].str())) {
408  try {
409  color_vec.push_back(color_t::from_hex_string(s));
410  } catch(const std::invalid_argument& e) {
411  ERR_NG << "Error parsing color list '" << key << "'.\n" << e.what();
412  color_vec.push_back(fallback);
413  }
414  }
415 
416  return color_vec;
417  };
418 
419  red_green_scale = parse_config_color_list("red_green_scale", {255, 255, 255});
420  red_green_scale_text = parse_config_color_list("red_green_scale_text", {255, 255, 255});
421  blue_white_scale = parse_config_color_list("blue_white_scale", {0 , 0 , 255});
422  blue_white_scale_text = parse_config_color_list("blue_white_scale_text", {0 , 0 , 255});
423 
424  server_list.clear();
425 
426  for(const config& server : v.child_range("server")) {
427  server_info sinf;
428  sinf.name = server["name"].str();
429  sinf.address = server["address"].str();
430  server_list.push_back(sinf);
431  }
432 
433  if(auto s = v.optional_child("sounds")) {
434  using namespace game_config::sounds;
435 
436  const auto load_attribute = [](const config& c, const std::string& key, std::string& member) {
437  if(c.has_attribute(key)) {
438  member = c[key].str();
439  }
440  };
441 
442  load_attribute(*s, "turn_bell", turn_bell);
443  load_attribute(*s, "timer_bell", timer_bell);
444  load_attribute(*s, "public_message", public_message);
445  load_attribute(*s, "private_message", private_message);
446  load_attribute(*s, "friend_message", friend_message);
447  load_attribute(*s, "server_message", server_message);
448  load_attribute(*s, "player_joins", player_joins);
449  load_attribute(*s, "player_leaves", player_leaves);
450  load_attribute(*s, "game_created", game_created);
451  load_attribute(*s, "game_user_arrive", game_user_arrive);
452  load_attribute(*s, "game_user_leave", game_user_leave);
453  load_attribute(*s, "ready_for_start", ready_for_start);
454  load_attribute(*s, "game_has_begun", game_has_begun);
455 
456  if(auto ss = s->optional_child("status")) {
457  using namespace game_config::sounds::status;
458 
459  load_attribute(*ss, "poisoned", poisoned);
460  load_attribute(*ss, "slowed", slowed);
461  load_attribute(*ss, "petrified", petrified);
462  }
463  }
464 }
465 
466 void add_color_info(const game_config_view& v, bool build_defaults)
467 {
468  if(build_defaults) {
469  default_colors.clear();
470  }
471 
472  for(const config& teamC : v.child_range("color_range")) {
473  const config::attribute_value* a1 = teamC.get("id"), *a2 = teamC.get("rgb");
474  if(!a1 || !a2) {
475  continue;
476  }
477 
478  std::string id = *a1;
479  std::vector<color_t> temp;
480 
481  for(const auto& s : utils::split(*a2)) {
482  try {
483  temp.push_back(color_t::from_hex_string(s));
484  } catch(const std::invalid_argument&) {
485  std::stringstream ss;
486  ss << "can't parse color string:\n" << teamC.debug() << "\n";
487  throw config::error(ss.str());
488  }
489  }
490 
491  std::optional<color_t> font_color = std::nullopt;
492  const config::attribute_value* fc = teamC.get("font_color");
493  if(fc) {
494  font_color = color_t::from_hex_string(fc->str());
495  }
496 
497  team_rgb_range.emplace(id, color_range(temp, font_color));
498  team_rgb_name.emplace(id, teamC["name"].t_str());
499 
500  LOG_NG << "registered color range '" << id << "': " << team_rgb_range[id].debug();
501 
502  // Generate palette of same name:
504 
505  if(build_defaults && teamC["default"].to_bool()) {
506  default_colors.push_back(id);
507  }
508  }
509 
510  for(const config &cp : v.child_range("color_palette")) {
511  for(const auto& [key, value] : cp.attribute_range()) {
512  std::vector<color_t> temp;
513  for(const auto& s : utils::split(value)) {
514  try {
515  temp.push_back(color_t::from_hex_string(s));
516  } catch(const std::invalid_argument& e) {
517  ERR_NG << "Invalid color in palette: " << s << " (" << e.what() << ")";
518  }
519  }
520 
521  team_rgb_colors.emplace(key, temp);
522  LOG_NG << "registered color palette: " << key;
523  }
524  }
525 }
526 
528 {
529  default_colors.clear();
530  team_rgb_colors.clear();
531  team_rgb_name.clear();
532  team_rgb_range.clear();
533 }
534 
535 const color_range& color_info(std::string_view name)
536 {
537  auto i = team_rgb_range.find(name);
538  if(i != team_rgb_range.end()) {
539  return i->second;
540  }
541 
542  std::vector<color_t> temp;
543  for(const auto& s : utils::split(name)) {
544  try {
545  temp.push_back(color_t::from_hex_string(s));
546  } catch(const std::invalid_argument&) {
547  throw config::error(_("Invalid color in range: ") + s);
548  }
549  }
550 
551  team_rgb_range.emplace(name, color_range(temp, std::nullopt));
552  return color_info(name);
553 }
554 
555 const std::vector<color_t>& tc_info(std::string_view name)
556 {
557  auto i = team_rgb_colors.find(name);
558  if(i != team_rgb_colors.end()) {
559  return i->second;
560  }
561 
562  std::vector<color_t> temp;
563  for(const auto& s : utils::split(name)) {
564  try {
565  temp.push_back(color_t::from_hex_string(s));
566  } catch(const std::invalid_argument& e) {
567  static std::vector<color_t> stv;
568  ERR_NG << "Invalid color in palette: " << s << " (" << e.what() << ")";
569  return stv;
570  }
571  }
572 
573  team_rgb_colors.emplace(name, temp);
574  return tc_info(name);
575 }
576 
577 color_t red_to_green(double val, bool for_text)
578 {
579  const std::vector<color_t>& color_scale = for_text ? red_green_scale_text : red_green_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 
587 color_t blue_to_white(double val, bool for_text)
588 {
589  const std::vector<color_t>& color_scale = for_text ? blue_white_scale_text : blue_white_scale;
590 
591  const double val_scaled = std::clamp(0.01 * val, 0.0, 1.0);
592  const int lvl = int(std::nearbyint((color_scale.size() - 1) * val_scaled));
593 
594  return color_scale[lvl];
595 }
596 
598 {
599  std::string ret = _("The Battle for Wesnoth") + " - " + revision;
600  return ret;
601 }
602 
603 } // game_config
#define debug(x)
Representing the "team color" information, this C++ class contains a subset of WML's [color_range] ta...
Definition: color_range.hpp:53
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
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:82
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 unreachable
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
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
The [color_range]id= attributes, equivalently the keys in the team_rgb_* maps, for which [color_range...
std::vector< color_t > blue_white_scale
std::string shroud_prefix
Definition: game_config.cpp:58
int reach_map_border_opacity
int reach_map_darken_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
bool reach_map_darken_tex
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
For colors defined by WML [color_range] tags, the palettes for team-based recoloring.
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)
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition: sound.cpp:139
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