The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
25
26#include <cmath>
27#include <random>
28
29static 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
33using namespace std::chrono_literals;
34
35namespace game_config
36{
37//
38// Gameplay constants
39//
43int recall_cost = 20;
46
49
51
52//
53// Terrain-related constants
54//
55unsigned int tile_size = 72;
56
57std::string default_terrain;
59
60std::vector<unsigned int> zoom_levels {36, 72, 144};
61
62//
63// Display scale constants
64//
65double hp_bar_scaling = 0.666;
66double xp_bar_scaling = 0.5;
67
68//
69// Misc
70//
71std::chrono::milliseconds lobby_network_timer = 100ms;
72std::chrono::milliseconds lobby_refresh = 4000ms;
73
74const std::size_t max_loop = 65536;
75
76std::vector<server_info> server_list;
77
78bool allow_insecure = false;
79bool addon_server_info = false;
80bool no_simd = false;
81
82//
83// Gamestate flags
84//
85bool
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
96const bool& debug = debug_impl;
97
98void 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
102 lg::severity 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
109 lg::severity 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
143std::vector<std::string> default_defeat_music;
144std::vector<std::string> default_victory_music;
145
146//
147// Color info
148//
149std::string flag_rgb, unit_rgb;
150
151std::vector<color_t> red_green_scale;
152std::vector<color_t> red_green_scale_text;
153
154std::vector<color_t> blue_white_scale;
155std::vector<color_t> blue_white_scale_text;
156
157std::map<std::string, color_range, std::less<>> team_rgb_range;
158// Map [color_range]id to [color_range]name, or "" if no name
159std::map<std::string, t_string, std::less<>> team_rgb_name;
160
161std::map<std::string, std::vector<color_t>, std::less<>> team_rgb_colors;
162
163std::vector<std::string> default_colors;
164
165namespace colors
166{
167std::string ally_orb_color;
168std::string enemy_orb_color;
169std::string moved_orb_color;
172std::string reach_map_color;
175} // namespace colors
176
177//
178// Image constants
179//
180
183
184namespace images {
185
186std::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//
231namespace sounds {
232
233std::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
248const 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
256namespace status {
257
258std::string
259 poisoned = "poison.ogg",
260 slowed = "slowed.wav",
261 petrified = "petrified.ogg";
262
263} // status
264
265} // sounds
266
267static void add_color_info(const game_config_view& v, bool build_defaults);
269{
270 add_color_info(v, false);
271}
272
273void 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
466void 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
535const 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
555const 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
577color_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
587color_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 ERR_NG
Representing the "team color" information, this C++ class contains a subset of WML's [color_range] ta...
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
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).
@ grid_bottom
Bottom half part of grid image.
@ grid_top
Top half part of grid image.
static lg::log_domain log_engine("engine")
#define ERR_NG
#define LOG_NG
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
const std::string ellipsis
Definition constants.cpp:39
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 game_title_background
std::string grid_bottom
std::string unreachable
std::string game_logo_background
std::string editor_brush
std::string battery_icon
std::string victory_laurel_easy
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
const std::string menu_contract
std::string game_user_leave
const std::string checkbox_release
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.
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.
std::string get_default_title_string()
bool ignore_replay_errors
std::string foot_teleport_enter
bool show_status_on_ally_orb
std::vector< color_t > red_green_scale_text
bool addon_server_info
const int gold_carryover_percentage
Default percentage gold carried over to the next scenario.
std::string foot_teleport_exit
std::string lobby_music
const std::vector< color_t > & tc_info(std::string_view name)
std::string fog_prefix
const bool & debug
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
int reach_map_border_opacity
int reach_map_darken_opacity
double hp_bar_scaling
bool disable_autosave
std::vector< server_info > server_list
std::string unit_rgb
unsigned int tile_size
bool allow_insecure
static void add_color_info(const game_config_view &v, bool build_defaults)
bool show_disengaged_orb
std::vector< unsigned int > zoom_levels
bool show_partial_orb
std::chrono::milliseconds lobby_refresh
std::string title_music
std::chrono::milliseconds lobby_network_timer
std::string default_terrain
bool show_unmoved_orb
std::string reach_map_prefix
std::vector< std::string > default_victory_music
int combat_experience
bool reach_map_darken_tex
void reset_color_info()
int reach_map_tint_opacity
const std::string revision
void set_debug(bool new_debug)
std::vector< color_t > red_green_scale
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
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
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
std::string address
may include ':' followed by port number
static map_location::direction s
#define i
#define e
#define c