1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #include "game_config.hpp"
16 
17 #include "color_range.hpp"
18 #include "config.hpp"
19 #include "gettext.hpp"
20 #include "log.hpp"
21 #include "utils/general.hpp"
22 #include "utils/math.hpp"
23 #include "game_version.hpp"
24 #include "wesconfig.h"
25 #include "serialization/string_utils.hpp"
26 #ifdef LOAD_REVISION
27 #include "revision.h"
28 #endif
29 
30 static lg::log_domain log_engine("engine");
31 #define LOG_NG LOG_STREAM(info, log_engine)
32 #define ERR_NG LOG_STREAM(err, log_engine)
33 
34 namespace game_config
35 {
36 
37 //
38 // Path and revision info
39 //
40 const std::string version = VERSION;
41 
42 const version_info wesnoth_version(VERSION);
43 const version_info min_savegame_version(MIN_SAVEGAME_VERSION);
44 const version_info test_version("test");
45 
46 #ifdef REVISION
47 const std::string revision = VERSION " (" REVISION ")";
48 #elif defined(VCS_SHORT_HASH) && defined(VCS_WC_MODIFIED)
49 const std::string revision = std::string(VERSION) + " (" + VCS_SHORT_HASH + (VCS_WC_MODIFIED ? "-Modified" : "-Clean") + ")";
50 #else
51 const std::string revision = VERSION;
52 #endif
53 
54 #ifdef WESNOTH_PATH
55 std::string path = WESNOTH_PATH;
56 #else
57 std::string path = "";
58 #endif
59 
60 #ifdef DEFAULT_PREFS_PATH
61 std::string default_preferences_path = DEFAULT_PREFS_PATH;
62 #else
63 std::string default_preferences_path = "";
64 #endif
65 
66 std::string wesnoth_program_dir;
67 
68 //
69 // Gameplay constants
70 //
71 int base_income      = 2;
72 int village_income   = 1;
73 int village_support  = 1;
74 int recall_cost      = 20;
75 int kill_experience  = 8;
76 
77 int poison_amount    = 8;
78 int rest_heal_amount = 2;
79 
80 const int gold_carryover_percentage = 80;
81 
82 //
83 // Terrain-related constants
84 //
85 unsigned int tile_size = 72;
86 
87 std::string default_terrain;
88 std::string shroud_prefix, fog_prefix;
89 
90 std::vector<unsigned int> zoom_levels {36, 72, 144};
91 
92 //
93 // Display scale constants
94 //
95 double hp_bar_scaling  = 0.666;
96 double xp_bar_scaling  = 0.5;
97 double hex_brightening = 1.25;
98 
99 //
100 // Misc
101 //
102 int cache_compression_level = 6;
103 
104 unsigned lobby_network_timer  = 100;
105 unsigned lobby_refresh        = 4000;
106 
107 const std::string observer_team_name = "observer";
108 
109 const size_t max_loop = 65536;
110 
111 std::vector<server_info> server_list;
112 
113 //
114 // Gamestate flags
115 //
116 bool
117 	debug_impl           = false,
118 	debug_lua            = false,
119 	editor               = false,
120 	ignore_replay_errors = false,
121 	mp_debug             = false,
122 	exit_at_end          = false,
123 	no_delay             = false,
124 	disable_autosave     = false,
125 	no_addons            = false;
126 
127 const bool& debug = debug_impl;
128 
set_debug(bool new_debug)129 void set_debug(bool new_debug) {
130 	if(debug_impl && !new_debug) {
131 		// Turning debug mode off; decrease deprecation severity
132 		int severity;
133 		if(lg::get_log_domain_severity("deprecation", severity)) {
134 			lg::set_log_domain_severity("deprecation", severity - 2);
135 		}
136 	} else if(!debug_impl && new_debug) {
137 		// Turning debug mode on; increase deprecation severity
138 		int severity;
139 		if(lg::get_log_domain_severity("deprecation", severity)) {
140 			lg::set_log_domain_severity("deprecation", severity + 2);
141 		}
142 	}
143 	debug_impl = new_debug;
144 }
145 
146 //
147 // Orb display flahs
148 //
149 bool
150 	show_ally_orb,
151 	show_enemy_orb,
152 	show_moved_orb,
153 	show_partial_orb,
154 	show_unmoved_orb;
155 
156 //
157 // Music constants
158 //
159 std::string title_music, lobby_music;
160 
161 std::vector<std::string> default_defeat_music;
162 std::vector<std::string> default_victory_music;
163 
164 //
165 // Color info
166 //
167 std::string flag_rgb, unit_rgb;
168 
169 std::vector<color_t> red_green_scale;
170 std::vector<color_t> red_green_scale_text;
171 
172 static std::vector<color_t> blue_white_scale;
173 static std::vector<color_t> blue_white_scale_text;
174 
175 std::map<std::string, color_range> team_rgb_range;
176 std::map<std::string, t_string> team_rgb_name;
177 
178 std::map<std::string, std::vector<color_t>> team_rgb_colors;
179 
180 std::vector<std::string> default_colors;
181 
182 namespace colors {
183 
184 std::string
185 	moved_orb_color,
186 	unmoved_orb_color,
187 	partial_orb_color,
188 	enemy_orb_color,
189 	ally_orb_color,
190 	default_color_list;
191 
192 } // colors
193 
194 //
195 // Image constants
196 //
197 std::vector<std::string> foot_speed_prefix;
198 
199 std::string foot_teleport_enter;
200 std::string foot_teleport_exit;
201 
202 namespace images {
203 
204 std::string
205 	game_title,
206 	game_title_background,
207 	game_logo,
208 	game_logo_background,
209 	victory_laurel,
210 	victory_laurel_hardest,
211 	victory_laurel_easy,
212 	// orbs and hp/xp bar
213 	orb,
214 	energy,
215 	// top bar icons
216 	battery_icon,
217 	time_icon,
218 	// flags
219 	flag,
220 	flag_icon,
221 	// hex overlay
222 	terrain_mask,
223 	grid_top,
224 	grid_bottom,
225 	mouseover,
226 	selected,
227 	editor_brush,
228 	unreachable,
229 	linger,
230 	// GUI elements
231 	observer,
232 	tod_bright,
233 	tod_dark,
234 	///@todo de-hardcode this
235 	selected_menu   = "buttons/radiobox-pressed.png",
236 	deselected_menu = "buttons/radiobox.png",
237 	checked_menu    = "buttons/checkbox-pressed.png",
238 	unchecked_menu  = "buttons/checkbox.png",
239 	wml_menu        = "buttons/WML-custom.png",
240 	level,
241 	ellipsis,
242 	missing,
243 	// notifications icon
244 	app_icon = "images/icons/icon-game.png";
245 
246 } //images
247 
248 //
249 // Sound constants
250 //
251 namespace sounds {
252 
253 std::string
254 	turn_bell        = "bell.wav",
255 	timer_bell       = "timer.wav",
256 	public_message   = "chat-[1~3].ogg",
257 	private_message  = "chat-highlight.ogg",
258 	friend_message   = "chat-friend.ogg",
259 	server_message   = "receive.wav",
260 	player_joins     = "arrive.wav",
261 	player_leaves    = "leave.wav",
262 	game_user_arrive = "join.wav",
263 	game_user_leave  = "leave.wav",
264 	ready_for_start  = "bell.wav",
265 	game_has_begun   = "gamestart.ogg",
266 	game_created	 = "join.wav";
267 
268 const std::string
269 	button_press     = "button.wav",
270 	checkbox_release = "checkbox.wav",
271 	slider_adjust    = "slider.wav",
272 	menu_expand      = "expand.wav",
273 	menu_contract    = "contract.wav",
274 	menu_select      = "select.wav";
275 
276 namespace status {
277 
278 std::string
279 	poisoned  = "poison.ogg",
280 	slowed    = "slowed.wav",
281 	petrified = "petrified.ogg";
282 
283 } // status
284 
285 } // sounds
286 
287 static void add_color_info(const config& v, bool build_defaults);
add_color_info(const config & v)288 void add_color_info(const config& v)
289 {
290 	add_color_info(v, false);
291 }
292 
load_config(const config & v)293 void load_config(const config &v)
294 {
295 	base_income      = v["base_income"].to_int(2);
296 	village_income   = v["village_income"].to_int(1);
297 	village_support  = v["village_support"].to_int(1);
298 	poison_amount    = v["poison_amount"].to_int(8);
299 	rest_heal_amount = v["rest_heal_amount"].to_int(2);
300 	recall_cost      = v["recall_cost"].to_int(20);
301 	kill_experience  = v["kill_experience"].to_int(8);
302 	lobby_refresh    = v["lobby_refresh"].to_int(2000);
303 	default_terrain  = v["default_terrain"].str();
304 	tile_size        = v["tile_size"].to_int(72);
305 
306 	std::vector<std::string> zoom_levels_str = utils::split(v["zoom_levels"]);
307 	if(!zoom_levels_str.empty()) {
308 		zoom_levels.clear();
309 		std::transform(zoom_levels_str.begin(), zoom_levels_str.end(), std::back_inserter(zoom_levels), [](const std::string zoom) {
310 			return static_cast<int>(std::stold(zoom) * tile_size);
311 		});
312 	}
313 
314 	title_music           = v["title_music"].str();
315 	lobby_music           = v["lobby_music"].str();
316 
317 	default_victory_music = utils::split(v["default_victory_music"].str());
318 	default_defeat_music  = utils::split(v["default_defeat_music"].str());
319 
320 	if(const config& i = v.child("colors")){
321 		using namespace game_config::colors;
322 
323 		moved_orb_color    = i["moved_orb_color"].str();
324 		unmoved_orb_color  = i["unmoved_orb_color"].str();
325 		partial_orb_color  = i["partial_orb_color"].str();
326 		enemy_orb_color    = i["enemy_orb_color"].str();
327 		ally_orb_color     = i["ally_orb_color"].str();
328 	} // colors
329 
330 	show_ally_orb     = v["show_ally_orb"].to_bool(true);
331 	show_enemy_orb    = v["show_enemy_orb"].to_bool(false);
332 	show_moved_orb    = v["show_moved_orb"].to_bool(true);
333 	show_partial_orb  = v["show_partly_orb"].to_bool(true);
334 	show_unmoved_orb  = v["show_unmoved_orb"].to_bool(true);
335 
336 	if(const config& i = v.child("images")){
337 		using namespace game_config::images;
338 
339 		game_title            = i["game_title"].str();
340 		game_title_background = i["game_title_background"].str();
341 		game_logo             = i["game_logo"].str();
342 		game_logo_background  = i["game_logo_background"].str();
343 
344 		victory_laurel = i["victory_laurel"].str();
345 		victory_laurel_hardest = i["victory_laurel_hardest"].str();
346 		victory_laurel_easy = i["victory_laurel_easy"].str();
347 
348 		orb    = i["orb"].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 		unreachable  = i["unreachable"].str();
364 		linger       = i["linger"].str();
365 
366 		observer   = i["observer"].str();
367 		tod_bright = i["tod_bright"].str();
368 		tod_dark   = i["tod_dark"].str();
369 		level      = i["level"].str();
370 		ellipsis   = i["ellipsis"].str();
371 		missing    = i["missing"].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 	hex_brightening = v["hex_brightening"].to_double(1.25);
377 
378 	foot_speed_prefix   = utils::split(v["footprint_prefix"]);
379 	foot_teleport_enter = v["footprint_teleport_enter"].str();
380 	foot_teleport_exit  = v["footprint_teleport_exit"].str();
381 
382 	shroud_prefix = v["shroud_prefix"].str();
383 	fog_prefix    = v["fog_prefix"].str();
384 
385 	add_color_info(v, true);
386 
387 	if(const config::attribute_value* a = v.get("flag_rgb")) {
388 		flag_rgb = a->str();
389 	}
390 
391 	if(const config::attribute_value* a = v.get("unit_rgb")) {
392 		unit_rgb = a->str();
393 	}
394 
395 	const auto parse_config_color_list = [&](
396 			const std::string& key,
397 			const color_t fallback)->std::vector<color_t>
398 	{
399 		std::vector<color_t> color_vec;
400 
401 		for(const auto& s : utils::split(v[key].str())) {
402 			try {
403 				color_vec.push_back(color_t::from_hex_string(s));
404 			} catch(const std::invalid_argument& e) {
405 				ERR_NG << "Error parsing color list '" << key << "'.\n" << e.what() << std::endl;
406 				color_vec.push_back(fallback);
407 			}
408 		}
409 
410 		return color_vec;
411 	};
412 
413 	red_green_scale       = parse_config_color_list("red_green_scale",       {255, 255, 255});
414 	red_green_scale_text  = parse_config_color_list("red_green_scale_text",  {255, 255, 255});
415 	blue_white_scale      = parse_config_color_list("blue_white_scale",      {0  , 0  , 255});
416 	blue_white_scale_text = parse_config_color_list("blue_white_scale_text", {0  , 0  , 255});
417 
418 	server_list.clear();
419 
420 	for(const config& server : v.child_range("server")) {
421         server_info sinf;
422         sinf.name = server["name"].str();
423         sinf.address = server["address"].str();
424         server_list.push_back(sinf);
425 	}
426 
427 	if(const config& s = v.child("sounds")) {
428 		using namespace game_config::sounds;
429 
430 		const auto load_attribute = [](const config& c, const std::string& key, std::string& member) {
431 			if(c.has_attribute(key)) {
432 				member = c[key].str();
433 			}
434 		};
435 
436 		load_attribute(s, "turn_bell",        turn_bell);
437 		load_attribute(s, "timer_bell",       timer_bell);
438 		load_attribute(s, "public_message",   public_message);
439 		load_attribute(s, "private_message",  private_message);
440 		load_attribute(s, "friend_message",   friend_message);
441 		load_attribute(s, "server_message",   server_message);
442 		load_attribute(s, "player_joins",     player_joins);
443 		load_attribute(s, "player_leaves",    player_leaves);
444 		load_attribute(s, "game_created",     game_created);
445 		load_attribute(s, "game_user_arrive", game_user_arrive);
446 		load_attribute(s, "game_user_leave",  game_user_leave);
447 		load_attribute(s, "ready_for_start",  ready_for_start);
448 		load_attribute(s, "game_has_begun",   game_has_begun);
449 
450 		if(const config & ss = s.child("status")) {
451 			using namespace game_config::sounds::status;
452 
453 			load_attribute(ss, "poisoned",  poisoned);
454 			load_attribute(ss, "slowed",    slowed);
455 			load_attribute(ss, "petrified", petrified);
456 		}
457 	}
458 }
459 
add_color_info(const config & v,bool build_defaults)460 void add_color_info(const config& v, bool build_defaults)
461 {
462 	if(build_defaults) {
463 		default_colors.clear();
464 	}
465 
466 	for(const config& teamC : v.child_range("color_range")) {
467 		const config::attribute_value* a1 = teamC.get("id"), *a2 = teamC.get("rgb");
468 		if(!a1 || !a2) {
469 			continue;
470 		}
471 
472 		std::string id = *a1;
473 		std::vector<color_t> temp;
474 
475 		for(const auto& s : utils::split(*a2)) {
476 			try {
477 				temp.push_back(color_t::from_hex_string(s));
478 			} catch(const std::invalid_argument&) {
479 				std::stringstream ss;
480 				ss << "can't parse color string:\n" << teamC.debug() << "\n";
481 				throw config::error(ss.str());
482 			}
483 		}
484 
485 		team_rgb_range.emplace(id, color_range(temp));
486 		team_rgb_name.emplace(id, teamC["name"].t_str());
487 
488 		LOG_NG << "registered color range '" << id << "': " << team_rgb_range[id].debug() << '\n';
489 
490 		// Ggenerate palette of same name;
491 		std::vector<color_t> tp = palette(team_rgb_range[id]);
492 		if(!tp.empty()) {
493 			team_rgb_colors.emplace(id, tp);
494 		}
495 
496 		if(build_defaults && teamC["default"].to_bool()) {
497 			default_colors.push_back(*a1);
498 		}
499 	}
500 
501 	for(const config &cp : v.child_range("color_palette")) {
502 		for(const config::attribute& rgb : cp.attribute_range()) {
503 			std::vector<color_t> temp;
504 			for(const auto& s : utils::split(rgb.second)) {
505 				try {
506 					temp.push_back(color_t::from_hex_string(s));
507 				} catch(const std::invalid_argument&) {
508 					ERR_NG << "Invalid color in palette: " << s << std::endl;
509 				}
510 			}
511 
512 			team_rgb_colors.emplace(rgb.first, temp);
513 			LOG_NG << "registered color palette: " << rgb.first << '\n';
514 		}
515 	}
516 }
517 
reset_color_info()518 void reset_color_info()
519 {
520 	default_colors.clear();
521 	team_rgb_colors.clear();
522 	team_rgb_name.clear();
523 	team_rgb_range.clear();
524 }
525 
color_info(const std::string & name)526 const color_range& color_info(const std::string& name)
527 {
528 	auto i = team_rgb_range.find(name);
529 	if(i != team_rgb_range.end()) {
530 		return i->second;
531 	}
532 
533 	std::vector<color_t> temp;
534 	for(const auto& s : utils::split(name)) {
535 		try {
536 			temp.push_back(color_t::from_hex_string(s));
537 		} catch(const std::invalid_argument&) {
538 			throw config::error(_("Invalid color in range: ") + s);
539 		}
540 	}
541 
542 	team_rgb_range.emplace(name, color_range(temp));
543 	return color_info(name);
544 }
545 
tc_info(const std::string & name)546 const std::vector<color_t>& tc_info(const std::string& name)
547 {
548 	auto i = team_rgb_colors.find(name);
549 	if(i != team_rgb_colors.end()) {
550 		return i->second;
551 	}
552 
553 	std::vector<color_t> temp;
554 	for(const auto& s : utils::split(name)) {
555 		try {
556 			temp.push_back(color_t::from_hex_string(s));
557 		} catch(const std::invalid_argument&) {
558 			static std::vector<color_t> stv;
559 			ERR_NG << "Invalid color in palette: " << s << std::endl;
560 			return stv;
561 		}
562 	}
563 
564 	team_rgb_colors.emplace(name, temp);
565 	return tc_info(name);
566 }
567 
red_to_green(int val,bool for_text)568 color_t red_to_green(int val, bool for_text)
569 {
570 	const std::vector<color_t>& color_scale = for_text ? red_green_scale_text : red_green_scale;
571 
572 	val = utils::clamp(val, 0, 100);
573 	const int lvl = (color_scale.size() - 1) * val / 100;
574 
575 	return color_scale[lvl];
576 }
577 
blue_to_white(int val,bool for_text)578 color_t blue_to_white(int val, bool for_text)
579 {
580 	const std::vector<color_t>& color_scale = for_text ? blue_white_scale_text : blue_white_scale;
581 
582 	val = utils::clamp(val, 0, 100);
583 	const int lvl = (color_scale.size() - 1) * val / 100;
584 
585 	return color_scale[lvl];
586 }
587 
get_default_title_string()588 std::string get_default_title_string()
589 {
590 	std::string ret = _("The Battle for Wesnoth") + " - " + revision;
591 	return ret;
592 }
593 
594 } // game_config
595