The Battle for Wesnoth  1.19.9+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 
81 //
82 // Gamestate flags
83 //
84 bool
85  debug_impl = false,
86  debug_lua = false,
87  strict_lua = false,
88  editor = false,
90  mp_debug = false,
91  exit_at_end = false,
93  no_addons = false;
94 
95 const bool& debug = debug_impl;
96 
97 void set_debug(bool new_debug) {
98  // TODO: remove severity static casts and fix issue #7894
99  if(debug_impl && !new_debug) {
100  // Turning debug mode off; decrease deprecation severity
102  if(lg::get_log_domain_severity("deprecation", severity)) {
103  int severityInt = static_cast<int>(severity);
104  lg::set_log_domain_severity("deprecation", static_cast<lg::severity>(severityInt - 2));
105  }
106  } else if(!debug_impl && new_debug) {
107  // Turning debug mode on; increase deprecation severity
109  if(lg::get_log_domain_severity("deprecation", severity)) {
110  int severityInt = static_cast<int>(severity);
111  lg::set_log_domain_severity("deprecation", static_cast<lg::severity>(severityInt + 2));
112  }
113  }
114  debug_impl = new_debug;
115 }
116 
117 //
118 // Orb display flags
119 //
127 
128 //
129 // Reach map opacity variables
130 //
131 
134 
135 //
136 // Music constants
137 //
139 
140 std::vector<std::string> default_defeat_music;
141 std::vector<std::string> default_victory_music;
142 
143 //
144 // Color info
145 //
146 std::string flag_rgb, unit_rgb;
147 
148 std::vector<color_t> red_green_scale;
149 std::vector<color_t> red_green_scale_text;
150 
151 std::vector<color_t> blue_white_scale;
152 std::vector<color_t> blue_white_scale_text;
153 
154 std::map<std::string, color_range, std::less<>> team_rgb_range;
155 // Map [color_range]id to [color_range]name, or "" if no name
156 std::map<std::string, t_string, std::less<>> team_rgb_name;
157 
158 std::map<std::string, std::vector<color_t>, std::less<>> team_rgb_colors;
159 
160 std::vector<std::string> default_colors;
161 
162 namespace colors
163 {
164 std::string ally_orb_color;
165 std::string enemy_orb_color;
166 std::string moved_orb_color;
167 std::string partial_orb_color;
168 std::string unmoved_orb_color;
169 std::string reach_map_color;
171 std::string default_color_list;
172 } // namespace colors
173 
174 //
175 // Image constants
176 //
177 std::vector<std::string> foot_speed_prefix;
178 
180 std::string foot_teleport_exit;
181 
182 namespace images {
183 
184 std::string
192  // orbs and hp/xp bar
196  // top bar icons
199  // flags
202  // hex overlay
210  // GUI elements
214  // TODO: de-hardcode this
215  selected_menu = "buttons/radiobox-pressed.png",
216  deselected_menu = "buttons/radiobox.png",
217  checked_menu = "buttons/checkbox-pressed.png",
218  unchecked_menu = "buttons/checkbox.png",
219  wml_menu = "buttons/WML-custom.png",
224  // notifications icon
225  app_icon = "images/icons/icon-game.png";
226 
227 } //images
228 
229 //
230 // Sound constants
231 //
232 namespace sounds {
233 
234 std::string
235  turn_bell = "bell.wav",
236  timer_bell = "timer.wav",
237  public_message = "chat-[1~3].ogg",
238  private_message = "chat-highlight.ogg",
239  friend_message = "chat-friend.ogg",
240  server_message = "receive.wav",
241  player_joins = "arrive.wav",
242  player_leaves = "leave.wav",
243  game_user_arrive = "join.wav",
244  game_user_leave = "leave.wav",
245  ready_for_start = "bell.wav",
246  game_has_begun = "gamestart.ogg",
247  game_created = "join.wav";
248 
249 const std::string
250  button_press = "button.wav",
251  checkbox_release = "checkbox.wav",
252  slider_adjust = "slider.wav",
253  menu_expand = "expand.wav",
254  menu_contract = "contract.wav",
255  menu_select = "select.wav";
256 
257 namespace status {
258 
259 std::string
260  poisoned = "poison.ogg",
261  slowed = "slowed.wav",
262  petrified = "petrified.ogg";
263 
264 } // status
265 
266 } // sounds
267 
268 static void add_color_info(const game_config_view& v, bool build_defaults);
270 {
271  add_color_info(v, false);
272 }
273 
274 void load_config(const config &v)
275 {
276  base_income = v["base_income"].to_int(2);
277  village_income = v["village_income"].to_int(1);
278  village_support = v["village_support"].to_int(1);
279  poison_amount = v["poison_amount"].to_int(8);
280  rest_heal_amount = v["rest_heal_amount"].to_int(2);
281  recall_cost = v["recall_cost"].to_int(20);
282  kill_experience = v["kill_experience"].to_int(8);
283  combat_experience= v["combat_experience"].to_int(1);
284  lobby_refresh = chrono::parse_duration(v["lobby_refresh"], 2000ms);
285  default_terrain = v["default_terrain"].str();
286  tile_size = v["tile_size"].to_int(72);
287 
288  std::vector<std::string> zoom_levels_str = utils::split(v["zoom_levels"]);
289  if(!zoom_levels_str.empty()) {
290  zoom_levels.clear();
291  std::transform(zoom_levels_str.begin(), zoom_levels_str.end(), std::back_inserter(zoom_levels), [](const std::string& zoom) {
292  int z = std::stoi(zoom);
293  if((z / 4) * 4 != z) {
294  ERR_NG << "zoom level " << z << " is not divisible by 4."
295  << " This will cause graphical glitches!";
296  }
297  return z;
298  });
299  }
300 
301  title_music = v["title_music"].str();
302  lobby_music = v["lobby_music"].str();
303 
304  default_victory_music = utils::split(v["default_victory_music"].str());
305  default_defeat_music = utils::split(v["default_defeat_music"].str());
306 
307  if(auto i = v.optional_child("colors")){
308  using namespace game_config::colors;
309 
310  moved_orb_color = i["moved_orb_color"].str();
311  unmoved_orb_color = i["unmoved_orb_color"].str();
312  partial_orb_color = i["partial_orb_color"].str();
313  enemy_orb_color = i["enemy_orb_color"].str();
314  ally_orb_color = i["ally_orb_color"].str();
315  reach_map_color = i["reach_map_color"].str();
316  reach_map_enemy_color = i["reach_map_enemy_color"].str();
317  } // colors
318 
319  show_ally_orb = v["show_ally_orb"].to_bool(true);
320  show_enemy_orb = v["show_enemy_orb"].to_bool(false);
321  show_moved_orb = v["show_moved_orb"].to_bool(true);
322  show_partial_orb = v["show_partial_orb"].to_bool(true);
323  show_status_on_ally_orb = v["show_status_on_ally_orb"].to_bool(true);
324  show_unmoved_orb = v["show_unmoved_orb"].to_bool(true);
325  show_disengaged_orb = v["show_disengaged_orb"].to_bool(true);
326 
327  if(auto i = v.optional_child("images")){
328  using namespace game_config::images;
329 
330  if (!i["game_title_background"].blank()) {
331  // Select a background at random
332  const auto backgrounds = utils::split(i["game_title_background"].str());
333  if (backgrounds.size() > 1) {
334  int r = rand() % (backgrounds.size());
335  game_title_background = backgrounds.at(r);
336  } else if (backgrounds.size() == 1) {
337  game_title_background = backgrounds.at(0);
338  }
339  }
340 
341  // Allow game_title to be empty
342  game_title = i["game_title"].str();
343  game_logo = i["game_logo"].str();
344  game_logo_background = i["game_logo_background"].str();
345 
346  victory_laurel = i["victory_laurel"].str();
347  victory_laurel_hardest = i["victory_laurel_hardest"].str();
348  victory_laurel_easy = i["victory_laurel_easy"].str();
349 
350  orb = i["orb"].str();
351  orb_two_color = i["orb_two_color"].str();
352  energy = i["energy"].str();
353 
354  battery_icon = i["battery_icon"].str();
355  time_icon = i["time_icon"].str();
356 
357  flag = i["flag"].str();
358  flag_icon = i["flag_icon"].str();
359 
360  terrain_mask = i["terrain_mask"].str();
361  grid_top = i["grid_top"].str();
362  grid_bottom = i["grid_bottom"].str();
363  mouseover = i["mouseover"].str();
364  selected = i["selected"].str();
365  editor_brush = i["editor_brush"].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_speed_prefix = utils::split(v["footprint_prefix"]);
381  foot_teleport_enter = v["footprint_teleport_enter"].str();
382  foot_teleport_exit = v["footprint_teleport_exit"].str();
383 
384  shroud_prefix = v["shroud_prefix"].str();
385  fog_prefix = v["fog_prefix"].str();
386  reach_map_prefix = v["reach_map_prefix"].str();
387  reach_map_border_opacity = v["reach_map_border_opacity"].to_int(100);
388  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
389 
391 
392  if(const config::attribute_value* a = v.get("flag_rgb")) {
393  flag_rgb = a->str();
394  }
395 
396  if(const config::attribute_value* a = v.get("unit_rgb")) {
397  unit_rgb = a->str();
398  }
399 
400  const auto parse_config_color_list = [&](
401  const std::string& key,
402  const color_t fallback)->std::vector<color_t>
403  {
404  std::vector<color_t> color_vec;
405 
406  for(const auto& s : utils::split(v[key].str())) {
407  try {
408  color_vec.push_back(color_t::from_hex_string(s));
409  } catch(const std::invalid_argument& e) {
410  ERR_NG << "Error parsing color list '" << key << "'.\n" << e.what();
411  color_vec.push_back(fallback);
412  }
413  }
414 
415  return color_vec;
416  };
417 
418  red_green_scale = parse_config_color_list("red_green_scale", {255, 255, 255});
419  red_green_scale_text = parse_config_color_list("red_green_scale_text", {255, 255, 255});
420  blue_white_scale = parse_config_color_list("blue_white_scale", {0 , 0 , 255});
421  blue_white_scale_text = parse_config_color_list("blue_white_scale_text", {0 , 0 , 255});
422 
423  server_list.clear();
424 
425  for(const config& server : v.child_range("server")) {
426  server_info sinf;
427  sinf.name = server["name"].str();
428  sinf.address = server["address"].str();
429  server_list.push_back(sinf);
430  }
431 
432  if(auto s = v.optional_child("sounds")) {
433  using namespace game_config::sounds;
434 
435  const auto load_attribute = [](const config& c, const std::string& key, std::string& member) {
436  if(c.has_attribute(key)) {
437  member = c[key].str();
438  }
439  };
440 
441  load_attribute(*s, "turn_bell", turn_bell);
442  load_attribute(*s, "timer_bell", timer_bell);
443  load_attribute(*s, "public_message", public_message);
444  load_attribute(*s, "private_message", private_message);
445  load_attribute(*s, "friend_message", friend_message);
446  load_attribute(*s, "server_message", server_message);
447  load_attribute(*s, "player_joins", player_joins);
448  load_attribute(*s, "player_leaves", player_leaves);
449  load_attribute(*s, "game_created", game_created);
450  load_attribute(*s, "game_user_arrive", game_user_arrive);
451  load_attribute(*s, "game_user_leave", game_user_leave);
452  load_attribute(*s, "ready_for_start", ready_for_start);
453  load_attribute(*s, "game_has_begun", game_has_begun);
454 
455  if(auto ss = s->optional_child("status")) {
456  using namespace game_config::sounds::status;
457 
458  load_attribute(*ss, "poisoned", poisoned);
459  load_attribute(*ss, "slowed", slowed);
460  load_attribute(*ss, "petrified", petrified);
461  }
462  }
463 }
464 
465 void add_color_info(const game_config_view& v, bool build_defaults)
466 {
467  if(build_defaults) {
468  default_colors.clear();
469  }
470 
471  for(const config& teamC : v.child_range("color_range")) {
472  const config::attribute_value* a1 = teamC.get("id"), *a2 = teamC.get("rgb");
473  if(!a1 || !a2) {
474  continue;
475  }
476 
477  std::string id = *a1;
478  std::vector<color_t> temp;
479 
480  for(const auto& s : utils::split(*a2)) {
481  try {
482  temp.push_back(color_t::from_hex_string(s));
483  } catch(const std::invalid_argument&) {
484  std::stringstream ss;
485  ss << "can't parse color string:\n" << teamC.debug() << "\n";
486  throw config::error(ss.str());
487  }
488  }
489 
490  team_rgb_range.emplace(id, color_range(temp));
491  team_rgb_name.emplace(id, teamC["name"].t_str());
492 
493  LOG_NG << "registered color range '" << id << "': " << team_rgb_range[id].debug();
494 
495  // Generate palette of same name;
496  team_rgb_colors.emplace(id, palette(team_rgb_range[id]));
497 
498  if(build_defaults && teamC["default"].to_bool()) {
499  default_colors.push_back(*a1);
500  }
501  }
502 
503  for(const config &cp : v.child_range("color_palette")) {
504  for(const auto& [key, value] : cp.attribute_range()) {
505  std::vector<color_t> temp;
506  for(const auto& s : utils::split(value)) {
507  try {
508  temp.push_back(color_t::from_hex_string(s));
509  } catch(const std::invalid_argument& e) {
510  ERR_NG << "Invalid color in palette: " << s << " (" << e.what() << ")";
511  }
512  }
513 
514  team_rgb_colors.emplace(key, temp);
515  LOG_NG << "registered color palette: " << key;
516  }
517  }
518 }
519 
521 {
522  default_colors.clear();
523  team_rgb_colors.clear();
524  team_rgb_name.clear();
525  team_rgb_range.clear();
526 }
527 
528 const color_range& color_info(std::string_view name)
529 {
530  auto i = team_rgb_range.find(name);
531  if(i != team_rgb_range.end()) {
532  return i->second;
533  }
534 
535  std::vector<color_t> temp;
536  for(const auto& s : utils::split(name)) {
537  try {
538  temp.push_back(color_t::from_hex_string(s));
539  } catch(const std::invalid_argument&) {
540  throw config::error(_("Invalid color in range: ") + s);
541  }
542  }
543 
544  team_rgb_range.emplace(name, color_range(temp));
545  return color_info(name);
546 }
547 
548 const std::vector<color_t>& tc_info(std::string_view name)
549 {
550  auto i = team_rgb_colors.find(name);
551  if(i != team_rgb_colors.end()) {
552  return i->second;
553  }
554 
555  std::vector<color_t> temp;
556  for(const auto& s : utils::split(name)) {
557  try {
558  temp.push_back(color_t::from_hex_string(s));
559  } catch(const std::invalid_argument& e) {
560  static std::vector<color_t> stv;
561  ERR_NG << "Invalid color in palette: " << s << " (" << e.what() << ")";
562  return stv;
563  }
564  }
565 
566  team_rgb_colors.emplace(name, temp);
567  return tc_info(name);
568 }
569 
570 color_t red_to_green(double val, bool for_text)
571 {
572  const std::vector<color_t>& color_scale = for_text ? red_green_scale_text : red_green_scale;
573 
574  const double val_scaled = std::clamp(0.01 * val, 0.0, 1.0);
575  const int lvl = int(std::nearbyint((color_scale.size() - 1) * val_scaled));
576 
577  return color_scale[lvl];
578 }
579 
580 color_t blue_to_white(double val, bool for_text)
581 {
582  const std::vector<color_t>& color_scale = for_text ? blue_white_scale_text : blue_white_scale;
583 
584  const double val_scaled = std::clamp(0.01 * val, 0.0, 1.0);
585  const int lvl = int(std::nearbyint((color_scale.size() - 1) * val_scaled));
586 
587  return color_scale[lvl];
588 }
589 
591 {
592  std::string ret = _("The Battle for Wesnoth") + " - " + revision;
593  return ret;
594 }
595 
596 } // 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:49
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
A class grating read only view to a vector of config objects, viewed as one config with all children ...
static game_config_view wrap(const config &cfg)
config_array_view child_range(config_key_type key) const
std::vector< color_t > 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:86
@ grid_bottom
Bottom half part of grid image.
@ grid_top
Top half part of grid image.
std::size_t i
Definition: function.cpp:1029
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:93
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:71
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 selected_menu
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 deselected_menu
std::string observer
std::string ellipsis
std::string unchecked_menu
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 checked_menu
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:61
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:89
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:92
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:97
std::vector< color_t > red_green_scale
bool exit_at_end
Definition: game_config.cpp:91
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:371
severity
Definition: log.hpp:83
bool set_log_domain_severity(const std::string &name, severity severity)
Definition: log.cpp:347
constexpr auto transform
Definition: ranges.hpp:41
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:59
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