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