The Battle for Wesnoth  1.19.4+dev
help_topic_generators.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 #define GETTEXT_DOMAIN "wesnoth-help"
17 
19 
20 #include "font/sdl_ttf_compat.hpp"
21 #include "formula/string_utils.hpp" // for VNGETTEXT
22 #include "game_config.hpp" // for debug, menu_contract, etc
23 #include "gettext.hpp" // for _, gettext, N_
24 #include "language.hpp" // for string_table, symbol_table
25 #include "log.hpp" // for LOG_STREAM, logger, etc
26 #include "movetype.hpp" // for movetype, movetype::effects, etc
27 #include "preferences/preferences.hpp" // for encountered_terrains, etc
28 #include "units/race.hpp" // for unit_race, etc
29 #include "terrain/terrain.hpp" // for terrain_type
30 #include "terrain/translation.hpp" // for operator==, ter_list, etc
31 #include "terrain/type_data.hpp" // for terrain_type_data, etc
32 #include "tstring.hpp" // for t_string, operator<<
33 #include "units/helper.hpp" // for resistance_color
34 #include "units/types.hpp" // for unit_type, unit_type_data, etc
35 #include "utils/optional_fwd.hpp"
36 #include "video.hpp" // fore current_resolution
37 
38 #include <set>
39 
40 static lg::log_domain log_help("help");
41 #define WRN_HP LOG_STREAM(warn, log_help)
42 #define DBG_HP LOG_STREAM(debug, log_help)
43 
44 namespace help {
45 
47 {
48  const t_string name;
49  const t_string id;
50  const int defense;
51  const int movement_cost;
52  const int vision_cost;
53  const int jamming_cost;
54  const bool defense_cap;
55 
56  bool operator<(const terrain_movement_info& other) const
57  {
58  return translation::icompare(name, other.name) < 0;
59  }
60 };
61 
62 static std::string best_str(bool best) {
63  std::string lang_policy = (best ? _("Best of") : _("Worst of"));
64  std::string color_policy = (best ? "green": "red");
65 
66  return "<span color='" + color_policy + "'>" + lang_policy + "</span>";
67 }
68 
69 typedef t_translation::ter_list::const_iterator ter_iter;
70 // Gets an english description of a terrain ter_list alias behavior: "Best of cave, hills", "Worst of Swamp, Forest" etc.
71 static std::string print_behavior_description(ter_iter start, ter_iter end, const std::shared_ptr<terrain_type_data> & tdata, bool first_level = true, bool begin_best = true)
72 {
73 
74  if (start == end) return "";
75  if (*start == t_translation::MINUS || *start == t_translation::PLUS) return print_behavior_description(start+1, end, tdata, first_level, *start == t_translation::PLUS); //absorb any leading mode changes by calling again, with a new default value begin_best.
76 
77  utils::optional<ter_iter> last_change_pos;
78 
79  bool best = begin_best;
80  for (ter_iter i = start; i != end; ++i) {
81  if ((best && *i == t_translation::MINUS) || (!best && *i == t_translation::PLUS)) {
82  best = !best;
83  last_change_pos = i;
84  }
85  }
86 
87  std::stringstream ss;
88 
89  if (!last_change_pos) {
90  std::vector<std::string> names;
91  for (ter_iter i = start; i != end; ++i) {
92  if (*i == t_translation::BASE) {
93  // TRANSLATORS: in a description of an overlay terrain, the terrain that it's placed on
94  names.push_back(_("base terrain"));
95  } else {
96  const terrain_type tt = tdata->get_terrain_info(*i);
97  if (!tt.editor_name().empty())
98  names.push_back(tt.editor_name());
99  }
100  }
101 
102  if (names.empty()) return "";
103  if (names.size() == 1) return names.at(0);
104 
105  ss << best_str(best) << " ";
106  if (!first_level) ss << "( ";
107  ss << names.at(0);
108 
109  for (std::size_t i = 1; i < names.size(); i++) {
110  ss << ", " << names.at(i);
111  }
112 
113  if (!first_level) ss << " )";
114  } else {
115  std::vector<std::string> names;
116  for (ter_iter i = *last_change_pos+1; i != end; ++i) {
117  const terrain_type tt = tdata->get_terrain_info(*i);
118  if (!tt.editor_name().empty())
119  names.push_back(tt.editor_name());
120  }
121 
122  if (names.empty()) { //This alias list is apparently padded with junk at the end, so truncate it without adding more parens
123  return print_behavior_description(start, *last_change_pos, tdata, first_level, begin_best);
124  }
125 
126  ss << best_str(best) << " ";
127  if (!first_level) ss << "( ";
128  ss << print_behavior_description(start, *last_change_pos-1, tdata, false, begin_best);
129  // Printed the (parenthesized) leading part from before the change, now print the remaining names in this group.
130  for (const std::string & s : names) {
131  ss << ", " << s;
132  }
133  if (!first_level) ss << " )";
134  }
135  return ss.str();
136 }
137 
139  std::stringstream ss;
140 
141  if (!type_.icon_image().empty())
142  ss << "<img>src='images/buttons/icon-base-32.png~RC(magenta>" << type_.id()
143  << ")~BLIT("<< "terrain/" << type_.icon_image() << "_30.png)" << "'</img>";
144 
145  if (!type_.editor_image().empty())
146  ss << "<img>src='" << type_.editor_image() << "'</img><br/>";
147 
148  if (!type_.help_topic_text().empty())
149  ss << "\n" << type_.help_topic_text().str() << "\n";
150  else
151  ss << "\n";
152 
153  std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data();
154 
155  if (!tdata) {
156  WRN_HP << "When building terrain help topics, we couldn't acquire any terrain types data";
157  return ss.str();
158  }
159 
160  // Special notes are generated from the terrain's properties - at the moment there's no way for WML authors
161  // to add their own via a [special_note] tag.
162  std::vector<std::string> special_notes;
163 
164  if(type_.is_village()) {
165  special_notes.push_back(_("Villages allow any unit stationed therein to heal, or to be cured of poison."));
166  } else if(type_.gives_healing() > 0) {
167  auto symbols = utils::string_map{{"amount", std::to_string(type_.gives_healing())}};
168  // TRANSLATORS: special note for terrains such as the oasis; the only terrain in core with this property heals 8 hp just like a village.
169  // For the single-hitpoint variant, the wording is different because I assume the player will be more interested in the curing-poison part than the minimal healing.
170  auto message = VNGETTEXT("This terrain allows units to be cured of poison, or to heal a single hitpoint.",
171  "This terrain allows units to heal $amount hitpoints, or to be cured of poison, as if stationed in a village.",
172  type_.gives_healing(), symbols);
173  special_notes.push_back(std::move(message));
174  }
175 
176  if(type_.is_castle()) {
177  special_notes.push_back(_("This terrain is a castle — units can be recruited onto it from a connected keep."));
178  }
179  if(type_.is_keep() && type_.is_castle()) {
180  // TRANSLATORS: The "this terrain is a castle" note will also be shown directly above this one.
181  special_notes.push_back(_("This terrain is a keep — a leader can recruit from this hex onto connected castle hexes."));
182  } else if(type_.is_keep() && !type_.is_castle()) {
183  // TRANSLATORS: Special note for a terrain, but none of the terrains in mainline do this.
184  special_notes.push_back(_("This unusual keep allows a leader to recruit while standing on it, but does not allow a leader on a connected keep to recruit onto this hex."));
185  }
186 
187  if(!special_notes.empty()) {
188  ss << "\n\n" << _("<header>Special Notes</header>") << "\n\n";
189  for(const auto& note : special_notes) {
190  ss << font::unicode_bullet << " " << note << '\n';
191  }
192  }
193 
194  // Almost all terrains will show the data in this conditional block. The ones that don't are the
195  // archetypes used in [movetype]'s subtags such as [movement_costs].
196  if (!type_.is_indivisible()) {
197  std::vector<t_string> underlying;
198  for (const auto& underlying_terrain : type_.union_type()) {
199  const terrain_type& base = tdata->get_terrain_info(underlying_terrain);
200  if (!base.editor_name().empty()) {
201  underlying.push_back(make_link(base.editor_name(), ".." + terrain_prefix + base.id()));
202  }
203  }
204  utils::string_map symbols;
205  symbols["types"] = utils::format_conjunct_list("", underlying);
206  // TRANSLATORS: $types is a conjunct list, typical values will be "Castle" or "Flat and Shallow Water".
207  // The terrain names will be hypertext links to the help page of the corresponding terrain type.
208  // There will always be at least 1 item in the list, but unlikely to be more than 3.
209  ss << "\n" << VNGETTEXT("Basic terrain type: $types", "Basic terrain types: $types", underlying.size(), symbols);
210 
211  if (type_.has_default_base()) {
212  const terrain_type& base = tdata->get_terrain_info(type_.default_base());
213 
214  symbols.clear();
215  if (base.is_indivisible()) {
216  symbols["type"] = make_link(base.editor_name(), ".." + terrain_prefix + base.id());
217  } else {
218  symbols["type"] = make_link(base.editor_name(), terrain_prefix + base.id());
219  }
220  // TRANSLATORS: In the help for a terrain type, for example Dwarven Village is often placed on Cave Floor
221  ss << "\n" << VGETTEXT("Typical base terrain: $type", symbols);
222  }
223 
224  ss << "\n";
225 
226  const t_translation::ter_list& underlying_mvt_terrains = type_.mvt_type();
227  ss << "\n" << _("Movement properties: ");
228  ss << print_behavior_description(underlying_mvt_terrains.begin(), underlying_mvt_terrains.end(), tdata) << "\n";
229 
230  const t_translation::ter_list& underlying_def_terrains = type_.def_type();
231  ss << "\n" << _("Defense properties: ");
232  ss << print_behavior_description(underlying_def_terrains.begin(), underlying_def_terrains.end(), tdata) << "\n";
233  }
234 
235  if (game_config::debug) {
236 
237  ss << "\n";
238  ss << "ID: " << type_.id() << "\n";
239 
240  ss << "Village: " << (type_.is_village() ? "Yes" : "No") << "\n";
241  ss << "Gives Healing: " << type_.gives_healing() << "\n";
242 
243  ss << "Keep: " << (type_.is_keep() ? "Yes" : "No") << "\n";
244  ss << "Castle: " << (type_.is_castle() ? "Yes" : "No") << "\n";
245 
246  ss << "Overlay: " << (type_.is_overlay() ? "Yes" : "No") << "\n";
247  ss << "Combined: " << (type_.is_combined() ? "Yes" : "No") << "\n";
248  ss << "Nonnull: " << (type_.is_nonnull() ? "Yes" : "No") << "\n";
249 
250  ss << "Terrain string: " << type_.number() << "\n";
251 
252  ss << "Hide in Editor: " << (type_.hide_in_editor() ? "Yes" : "No") << "\n";
253  ss << "Editor Group: " << type_.editor_group() << "\n";
254 
255  ss << "Light Bonus: " << type_.light_bonus(0) << "\n";
256 
257  ss << type_.income_description();
258 
259  if (type_.editor_image().empty()) { // Note: this is purely temporary to help make a different help entry
260  ss << "\nEditor Image: Empty\n";
261  } else {
262  ss << "\nEditor Image: " << type_.editor_image() << "\n";
263  }
264 
265  const t_translation::ter_list& underlying_mvt_terrains = tdata->underlying_mvt_terrain(type_.number());
266  ss << "\nDebug Mvt Description String:";
267  for (const t_translation::terrain_code & t : underlying_mvt_terrains) {
268  ss << " " << t;
269  }
270 
271  const t_translation::ter_list& underlying_def_terrains = tdata->underlying_def_terrain(type_.number());
272  ss << "\nDebug Def Description String:";
273  for (const t_translation::terrain_code & t : underlying_def_terrains) {
274  ss << " " << t;
275  }
276 
277  }
278 
279  return ss.str();
280 }
281 
282 
283 //Typedef to help with formatting list of traits
284 // Maps localized trait name to trait help topic ID
285 typedef std::pair<std::string, std::string> trait_data;
286 
287 //Helper function for printing a list of trait data
288 static void print_trait_list(std::stringstream & ss, const std::vector<trait_data> & l)
289 {
290  std::size_t i = 0;
291  ss << make_link(l[i].first, l[i].second);
292 
293  // This doesn't skip traits with empty names
294  for(i++; i < l.size(); i++) {
295  ss << ", " << make_link(l[i].first,l[i].second);
296  }
297 }
298 
299 std::string unit_topic_generator::operator()() const {
300  // Force the lazy loading to build this unit.
302 
303  std::stringstream ss;
304  std::string clear_stringstream;
305  const std::string detailed_description = type_.unit_description();
308 
309  const int screen_width = video::game_canvas_size().x;
310 
311  ss << _("Level") << " " << type_.level();
312 
313  // Portraits
314  const std::string &male_portrait = male_type.small_profile().empty() ?
315  male_type.big_profile() : male_type.small_profile();
316  const std::string &female_portrait = female_type.small_profile().empty() ?
317  female_type.big_profile() : female_type.small_profile();
318 
319  const bool has_male_portrait = !male_portrait.empty() && male_portrait != male_type.image() && male_portrait != "unit_image";
320  const bool has_female_portrait = !female_portrait.empty() && female_portrait != male_portrait && female_portrait != female_type.image() && female_portrait != "unit_image";
321 
322  int sz = (has_male_portrait && has_female_portrait ? 300 : 400);
323  if (screen_width <= 1366) {
324  sz = (has_male_portrait && has_female_portrait ? 200 : 300);
325  } else if (screen_width >= 1920) {
326  sz = 400;
327  }
328 
329  // without this, scaling down (SCALE_INTO below) and then scaling back up due to the pixel multiplier leads to ugly results
330  // can't use the preferences value since it may be different than the actual value
331  sz *= video::get_pixel_scale();
332 
333  // TODO: figure out why the second checks don't match but the last does
334  if (has_male_portrait) {
335  ss << "<img src='" << male_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes' />";
336  }
337 
338  if (has_female_portrait) {
339  ss << "<img src='" << female_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes' />";
340  }
341 
342  // Unit Images
343  ss << "<img src='" << male_type.image();
344  ss << "~RC(" << male_type.flag_rgb() << ">red)";
345  if (screen_width >= 1200) { ss << "~SCALE_SHARP(200%,200%)"; };
346  ss << "' box='no' />";
347 
348  if (female_type.image() != male_type.image()) {
349  ss << "<img src='" << female_type.image();
350  ss << "~RC(" << female_type.flag_rgb() << ">red)";
351  if (screen_width >= 1200) { ss << "~SCALE_SHARP(200%,200%)"; };
352  ss << "' box='no' />";
353  }
354 
355  ss << "\n";
356 
357  // Print cross-references to units that this unit advances from/to.
358  // Cross reference to the topics containing information about those units.
359  const bool first_reverse_value = true;
360  bool reverse = first_reverse_value;
361  if (variation_.empty()) {
362  do {
363  std::vector<std::string> adv_units =
364  reverse ? type_.advances_from() : type_.advances_to();
365  bool first = true;
366 
367  for (const std::string &adv : adv_units) {
369  if (!type || type->hide_help()) {
370  continue;
371  }
372 
373  if (first) {
374  if (reverse) {
375  ss << _("Advances from: ");
376  } else {
377  ss << _("Advances to: ");
378  }
379  first = false;
380  } else {
381  ss << ", ";
382  }
383 
384  std::string lang_unit = type->type_name();
385  std::string ref_id;
387  const std::string section_prefix = type->show_variations_in_help() ? ".." : "";
388  ref_id = section_prefix + unit_prefix + type->id();
389  } else {
390  ref_id = unknown_unit_topic;
391  lang_unit += " (?)";
392  }
393  ss << make_link(lang_unit, ref_id);
394  }
395  if (!first) {
396  ss << "\n";
397  }
398 
399  reverse = !reverse; //switch direction
400  } while(reverse != first_reverse_value); // don't restart
401  }
402 
403  const unit_type* parent = variation_.empty() ? &type_ :
405  if (!variation_.empty()) {
406  ss << _("Base unit: ") << make_link(parent->type_name(), ".." + unit_prefix + type_.id()) << "\n";
407  } else {
408  bool first = true;
409  for (const std::string& base_id : utils::split(type_.get_cfg()["base_ids"])) {
410  if (first) {
411  ss << _("Base units: ");
412  first = false;
413  }
414  const unit_type* base_type = unit_types.find(base_id, unit_type::HELP_INDEXED);
415  const std::string section_prefix = base_type->show_variations_in_help() ? ".." : "";
416  ss << make_link(base_type->type_name(), section_prefix + unit_prefix + base_id) << "\n";
417  }
418  }
419 
420  bool first = true;
421  for (const std::string &var_id : parent->variations()) {
422  const unit_type &type = parent->get_variation(var_id);
423 
424  if(type.hide_help()) {
425  continue;
426  }
427 
428  if (first) {
429  ss << _("Variations: ");
430  first = false;
431  } else {
432  ss << ", ";
433  }
434 
435  std::string ref_id;
436 
437  std::string var_name = type.variation_name();
439  ref_id = variation_prefix + type.id() + "_" + var_id;
440  } else {
441  ref_id = unknown_unit_topic;
442  var_name += " (?)";
443  }
444 
445  ss << make_link(var_name, ref_id);
446  }
447 
448  // Print the race of the unit, cross-reference it to the respective topic.
449  const std::string race_id = type_.race_id();
450  std::string race_name = type_.race()->plural_name();
451  if (race_name.empty()) {
452  race_name = _ ("race^Miscellaneous");
453  }
454  ss << _("Race: ");
455  ss << make_link(race_name, "..race_" + race_id);
456  ss << "\n";
457 
458  // Print the possible traits of the unit, cross-reference them
459  // to their respective topics.
461  std::vector<trait_data> must_have_traits;
462  std::vector<trait_data> random_traits;
463  int must_have_nameless_traits = 0;
464 
465  for(const config& trait : traits) {
466  const std::string& male_name = trait["male_name"].str();
467  const std::string& female_name = trait["female_name"].str();
468  std::string trait_name;
469  if (type_.has_gender_variation(unit_race::MALE) && ! male_name.empty())
470  trait_name = male_name;
471  else if (type_.has_gender_variation(unit_race::FEMALE) && ! female_name.empty())
472  trait_name = female_name;
473  else if (! trait["name"].str().empty())
474  trait_name = trait["name"].str();
475  else
476  continue; // Hidden trait
477 
478  std::string lang_trait_name = translation::gettext(trait_name.c_str());
479  if (lang_trait_name.empty() && trait["availability"].str() == "musthave") {
480  ++must_have_nameless_traits;
481  continue;
482  }
483  const std::string ref_id = "traits_"+trait["id"].str();
484  ((trait["availability"].str() == "musthave") ? must_have_traits : random_traits).emplace_back(lang_trait_name, ref_id);
485  }
486 
487  bool line1 = !must_have_traits.empty();
488  bool line2 = !random_traits.empty() && type_.num_traits() > must_have_traits.size();
489 
490  if (line1) {
491  std::string traits_label = _("Traits");
492  ss << traits_label;
493  if (line2) {
494  std::stringstream must_have_count;
495  must_have_count << "\n (" << must_have_traits.size() << ") : ";
496  std::stringstream random_count;
497  random_count << " (" << (type_.num_traits() - must_have_traits.size() - must_have_nameless_traits) << ") : ";
498  ss << must_have_count.str();
499  print_trait_list(ss, must_have_traits);
500  ss << "\n" << random_count.str();
501  print_trait_list(ss, random_traits);
502  } else {
503  ss << ": ";
504  print_trait_list(ss, must_have_traits);
505  }
506  ss << "\n";
507  } else {
508  if (line2) {
509  ss << _("Traits") << " (" << (type_.num_traits() - must_have_nameless_traits) << ") : ";
510  print_trait_list(ss, random_traits);
511  ss << "\n";
512  }
513  }
514  }
515 
516  // Print the abilities the units has, cross-reference them
517  // to their respective topics. TODO: Update this according to musthave trait effects, similar to movetype below
518  if(!type_.abilities_metadata().empty()) {
519  ss << _("Abilities: ");
520 
521  bool start = true;
522 
523  for(auto iter = type_.abilities_metadata().begin(); iter != type_.abilities_metadata().end(); ++iter) {
524  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
525 
526  if(iter->name.empty()) {
527  continue;
528  }
529 
530  if(!start) {
531  ss << ", ";
532  } else {
533  start = false;
534  }
535 
536  std::string lang_ability = translation::gettext(iter->name.c_str());
537  ss << make_link(lang_ability, ref_id);
538  }
539 
540  ss << "\n\n";
541  }
542 
543  // Print the extra AMLA upgrade abilities, cross-reference them to their respective topics.
544  if(!type_.adv_abilities_metadata().empty()) {
545  ss << _("Ability Upgrades: ");
546 
547  bool start = true;
548 
549  for(auto iter = type_.adv_abilities_metadata().begin(); iter != type_.adv_abilities_metadata().end(); ++iter) {
550  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
551 
552  if(iter->name.empty()) {
553  continue;
554  }
555 
556  if(!start) {
557  ss << ", ";
558  } else {
559  start = false;
560  }
561 
562  std::string lang_ability = translation::gettext(iter->name.c_str());
563  ss << make_link(lang_ability, ref_id);
564  }
565 
566  ss << "\n\n";
567  }
568 
569  // Print some basic information such as HP and movement points.
570  // TODO: Make this info update according to musthave traits, similar to movetype below.
571 
572  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
573  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
574  // unpleasant line breaks (issue #3256).
575  ss << _("HP:") << font::nbsp << type_.hitpoints() << " "
576  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
577  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
578  // unpleasant line breaks (issue #3256).
579  << _("Moves:") << font::nbsp << type_.movement() << " ";
580  if (type_.vision() != type_.movement()) {
581  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
582  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
583  // unpleasant line breaks (issue #3256).
584  ss << _("Vision:") << font::nbsp << type_.vision() << " ";
585  }
586  if (type_.jamming() > 0) {
587  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
588  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
589  // unpleasant line breaks (issue #3256).
590  ss << _("Jamming:") << font::nbsp << type_.jamming() << " ";
591  }
592  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
593  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
594  // unpleasant line breaks (issue #3256).
595  ss << _("Cost:") << font::nbsp << type_.cost() << " "
596  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
597  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
598  // unpleasant line breaks (issue #3256).
599  << _("Alignment:") << font::nbsp
600  << make_link(type_.alignment_description(type_.alignment(), type_.genders().front()), "time_of_day")
601  << " ";
603  // TRANSLATORS: This string is used in the help page of a single unit. It uses
604  // non-breaking spaces to prevent unpleasant line breaks (issue #3256). In the
605  // translation use non-breaking spaces as appropriate for the target language.
606  ss << _("Required\u00a0XP:") << font::nbsp << type_.experience_needed();
607  }
608 
609  // Print the detailed description about the unit.
610  ss << "\n" << detailed_description;
611 
612  if(const auto notes = type_.special_notes(); !notes.empty()) {
613  ss << "\n<header>" << _("Special Notes") << "</header>\n";
614  for(const auto& note : notes) {
615  ss << font::unicode_bullet << " <i>" << note << "</i>" << '\n';
616  }
617  }
618 
619  // Print the attacks table
620  ss << "\n<header>" << _("Attacks") << "</header>";
621 
622  if (!type_.attacks().empty()) {
623  // Start table
624  ss << "<table>";
625 
626  // Print headers for the table.
627  ss
628  << "<row>"
629  << "<col><b>" << _("Icon") << "</b></col>"
630  << "<col><b>" << _("Name") << "</b></col>"
631  << "<col><b>" << _("Strikes") << "</b></col>"
632  << "<col><b>" << _("Range") << "</b></col>"
633  << "<col><b>" << _("Type") << "</b></col>"
634  << "<col><b>" << _("Special") << "</b></col>"
635  << "</row>";
636 
637  std::stringstream attack_ss;
638 
639  // Print information about every attack.
640  for(const attack_type& attack : type_.attacks()) {
641  std::string lang_weapon = attack.name();
642  std::string lang_type = string_table["type_" + attack.type()];
643 
644  attack_ss << "<row>";
645 
646  // Attack icon
647  attack_ss << "<col><img src='" << attack.icon() << "'/></col>";
648 
649  // attack name
650  attack_ss << "<col>" << lang_weapon << "</col>";
651 
652  // damage x strikes
653  attack_ss << "<col>" << attack.damage() << font::weapon_numbers_sep << attack.num_attacks()
654  << " " << attack.accuracy_parry_description() << "</col>";
655 
656  // range
657  const std::string range_icon = "icons/profiles/" + attack.range() + "_attack.png~SCALE_INTO(16,16)";
658  attack_ss << "<col>" << "<img src='" << range_icon << "'/>";
659  if (attack.min_range() > 1 || attack.max_range() > 1) {
660  attack_ss << attack.min_range() << "-" << attack.max_range() << ' ';
661  }
662  attack_ss << string_table["range_" + attack.range()] << "</col>";
663 
664  // type
665  const std::string type_icon = "icons/profiles/" + attack.type() + ".png~SCALE_INTO(16,16)";
666  attack_ss << "<col>" << "<img src='" << type_icon << "'/>";
667  attack_ss << lang_type << "</col>";
668 
669  // special
670  attack_ss << "<col>";
671  std::vector<std::pair<t_string, t_string>> specials = attack.special_tooltips();
672  if (!specials.empty()) {
673  std::string lang_special = "";
674  const std::size_t specials_size = specials.size();
675  for (std::size_t i = 0; i != specials_size; ++i) {
676  const std::string ref_id = std::string("weaponspecial_")
677  + specials[i].first.base_str();
678  lang_special = (specials[i].first);
679  attack_ss << make_link(lang_special, ref_id);
680  if (i+1 != specials_size) {
681  attack_ss << ", "; //comma placed before next special
682  }
683  }
684  } else {
685  attack_ss << "none";
686  }
687  attack_ss << "</col>";
688  attack_ss << "</row>";
689  }
690 
691  ss << attack_ss.str();
692  ss << "</table>";
693  }
694 
695  // Generate the movement type of the unit, with resistance, defense, movement, jamming and vision data updated according to any 'musthave' traits which always apply
696  movetype movement_type = type_.movement_type();
698  if (!traits.empty() && type_.num_traits() > 0) {
699  for (const config & t : traits) {
700  if (t["availability"].str() == "musthave") {
701  for (const config & effect : t.child_range("effect")) {
702  if (!effect.has_child("filter") // If this is musthave but has a unit filter, it might not always apply, so don't apply it in the help.
703  && movetype::effects.find(effect["apply_to"].str()) != movetype::effects.end()) {
704  movement_type.merge(effect, effect["replace"].to_bool());
705  }
706  }
707  }
708  }
709  }
710 
711  const bool has_terrain_defense_caps = movement_type.has_terrain_defense_caps(prefs::get().encountered_terrains());
712  const bool has_vision = type_.movement_type().has_vision_data();
713  const bool has_jamming = type_.movement_type().has_jamming_data();
714 
715  // Print the resistance table of the unit.
716  ss << "\n<header>" << _("Resistances") << "</header>";
717 
718  // Start table
719  ss << "<table>";
720  ss << "<row>";
721  ss << "<col><b>" << _("Attack Type") << "</b></col>";
722  ss << "<col><b>" << _("Resistance") << "</b></col>";
723  ss << "</row>";
724 
725  utils::string_map_res dam_tab = movement_type.damage_table();
726  for(std::pair<std::string, std::string> dam_it : dam_tab) {
727  int resistance = 100;
728  try {
729  resistance -= std::stoi(dam_it.second);
730  } catch(std::invalid_argument&) {}
731  std::string resist = std::to_string(resistance) + '%';
732  const std::size_t pos = resist.find('-');
733  if (pos != std::string::npos) {
734  resist.replace(pos, 1, font::unicode_minus);
735  }
736  std::string color = unit_helper::resistance_color(resistance);
737  const std::string lang_type = string_table["type_" + dam_it.first];
738  const std::string type_icon = "icons/profiles/" + dam_it.first + ".png~SCALE_INTO(16,16)";
739  ss << "<row>";
740  ss << "<col>" << "<img src='" << type_icon << "'/>" << lang_type << "</col>";
741  std::stringstream str;
742  str << "<span color='" << color << "' text='"<< resist << "'/>";
743  ss << "<col>" << str.str() << "</col>";
744  ss << "</row>";
745  }
746 
747  ss << "</table>";
748 
749  if (std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data()) {
750  // Print the terrain modifier table of the unit.
751  ss << "\n<header>" << _("Terrain Modifiers") << "</header>";
752  ss << "<table>";
753  ss << "<row>";
754  ss << "<col><b>" << _("Terrain") << "</b></col>";
755  ss << "<col><b>" << _("Defense") << "</b></col>";
756  ss << "<col><b>" << _("Movement Cost") << "</b></col>";
757  if (has_terrain_defense_caps) { ss << "<col><b>" << _("Defense Cap") << "</b></col>"; }
758  if (has_vision) { ss << "<col><b>" << _("Vision Cost") << "</b></col>"; }
759  if (has_jamming) { ss << "<col><b>" << _("Jamming Cost") << "</b></col>"; }
760  ss << "</row>";
761 
762  std::set<terrain_movement_info> terrain_moves;
763 
764  for (t_translation::terrain_code terrain : prefs::get().encountered_terrains()) {
766  continue;
767  }
768  const terrain_type& info = tdata->get_terrain_info(terrain);
769  const int moves = movement_type.movement_cost(terrain);
770  const bool cannot_move = moves > type_.movement();
771  if (cannot_move && info.hide_if_impassable()) {
772  continue;
773  }
774 
775  if (info.is_indivisible() && info.is_nonnull()) {
776  terrain_movement_info movement_info =
777  {
778  info.name(),
779  info.id(),
780  100 - movement_type.defense_modifier(terrain),
781  moves,
782  movement_type.vision_cost(terrain),
783  movement_type.jamming_cost(terrain),
784  movement_type.get_defense().capped(terrain)
785  };
786 
787  terrain_moves.insert(movement_info);
788  }
789  }
790 
791  for(const terrain_movement_info &m : terrain_moves)
792  {
793 
794  bool high_res = false;
795  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
796  const std::string terrain_image = "icons/terrain/terrain_type_" + m.id + (high_res ? "_30.png" : ".png");
797 
798  const std::string final_image = tc_base + "~RC(magenta>" + m.id + ")~BLIT(" + terrain_image + ")";
799 
800  ss << "<row>";
801  ss << "<col>" << "<img src='" + final_image + "'/>" + make_link(m.name, "..terrain_" + m.id) << "</col>";
802 
803  //defense - range: +10 % .. +70 %
804  // passing false to select the more saturated red-to-green scale
805  std::string color = game_config::red_to_green(m.defense, false).to_hex_string();
806 
807  std::stringstream str;
808  std::stringstream str_unformatted;
809  str << "<span color='" << color << "'>"<< m.defense << "%</span>";
810  str_unformatted << m.defense << "%";
811  ss << "<col>" << str.str() << "</col>";
812 
813  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
814  str.str(clear_stringstream);
815  str_unformatted.str(clear_stringstream);
816  const bool cannot_move = m.movement_cost > type_.movement(); // cannot move in this terrain
817  double movement_red_to_green = 100.0 - 25.0 * m.movement_cost;
818 
819  // passing true to select the less saturated red-to-green scale
820  std::string movement_color = game_config::red_to_green(movement_red_to_green, true).to_hex_string();
821  str << "<span color='" << movement_color << "'>";
822  // A 5 MP margin; if the movement costs go above
823  // the unit's max moves + 5, we replace it with dashes.
824  if(cannot_move && (m.movement_cost > type_.movement() + 5)) {
825  str_unformatted << font::unicode_figure_dash;
826  } else if(cannot_move) {
827  str_unformatted << "(" << m.movement_cost << ")";
828  } else {
829  str_unformatted << m.movement_cost;
830  }
831  if(m.movement_cost != 0) {
832  const int movement_hexes_per_turn = type_.movement() / m.movement_cost;
833  str_unformatted << " ";
834  for(int i = 0; i < movement_hexes_per_turn; ++i) {
835  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
836  str_unformatted << "\u2b23\u200b";
837  }
838  }
839  str << str_unformatted.str() << "</span>";
840  ss << "<col>" << str.str() << "</col>";
841 
842  //defense cap
843  if (has_terrain_defense_caps) {
844  str.str(clear_stringstream);
845  str_unformatted.str(clear_stringstream);
846  if (m.defense_cap) {
847  str << "<span color='"<< color <<"'>" << m.defense << "%</span>";
848  str_unformatted << m.defense << "%";
849  } else {
850  str << "<span color='white'>" << font::unicode_figure_dash << "</span>";
851  str_unformatted << font::unicode_figure_dash;
852  }
853  ss << "<col>" << str.str() << "</col>";
854  }
855 
856  //vision
857  if (has_vision) {
858  str.str(clear_stringstream);
859  str_unformatted.str(clear_stringstream);
860  const bool cannot_view = m.vision_cost > type_.vision(); // cannot view in this terrain
861  double vision_red_to_green = 100.0 - 25.0 * m.vision_cost;
862 
863  // passing true to select the less saturated red-to-green scale
864  std::string vision_color = game_config::red_to_green(vision_red_to_green, true).to_hex_string();
865  str << "<span color='" << vision_color << "'>";
866  // A 5 MP margin; if the vision costs go above
867  // the unit's vision + 5, we replace it with dashes.
868  if(cannot_view && (m.vision_cost > type_.vision() + 5)) {
869  str_unformatted << font::unicode_figure_dash;
870  } else if(cannot_view) {
871  str_unformatted << "(" << m.vision_cost << ")";
872  } else {
873  str_unformatted << m.vision_cost;
874  }
875  if(m.vision_cost != 0) {
876  const int vision_hexes_per_turn = type_.vision() / m.vision_cost;
877  str_unformatted << " ";
878  for(int i = 0; i < vision_hexes_per_turn; ++i) {
879  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
880  str_unformatted << "\u2b23\u200b";
881  }
882  }
883  str << str_unformatted.str() << "</span>";
884  ss << "<col>" << str.str() << "</col>";
885  }
886 
887  //jamming
888  if (has_jamming) {
889  str.str(clear_stringstream);
890  str_unformatted.str(clear_stringstream);
891  const bool cannot_jam = m.jamming_cost > type_.jamming(); // cannot jam in this terrain
892  double jamming_red_to_green = 100.0 - 25.0 * m.jamming_cost;
893 
894  // passing true to select the less saturated red-to-green scale
895  std::string jamming_color = game_config::red_to_green(jamming_red_to_green, true).to_hex_string();
896  str << "<span color='" << jamming_color << "'>";
897  // A 5 MP margin; if the jamming costs go above
898  // the unit's jamming + 5, we replace it with dashes.
899  if (cannot_jam && m.jamming_cost > type_.jamming() + 5) {
900  str_unformatted << font::unicode_figure_dash;
901  } else if(cannot_jam) {
902  str_unformatted << "(" << m.jamming_cost << ")";
903  } else {
904  str_unformatted << m.jamming_cost;
905  }
906  if(m.jamming_cost != 0) {
907  const int jamming_hexes_per_turn = type_.jamming() / m.jamming_cost;
908  str_unformatted << " ";
909  for(int i = 0; i < jamming_hexes_per_turn; ++i) {
910  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
911  str_unformatted << "\u2b23\u200b";
912  }
913  }
914  str << str_unformatted.str() << "</span>";
915  ss << "<col>" << str.str() << "</col>";
916  }
917 
918  ss << "</row>";
919  }
920 
921  ss << "</table>";
922 
923  } else {
924  WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.";
925  }
926 
927  return ss.str();
928 }
929 
930 void unit_topic_generator::push_header(std::vector< item > &row, const std::string& name) const {
932 }
933 
934 } // end namespace help
double t
Definition: astarsearch.cpp:63
std::vector< std::string > names
Definition: build_info.cpp:67
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:163
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:287
virtual std::string operator()() const
virtual std::string operator()() const
void push_header(std::vector< help::item > &row, const std::string &name) const
bool capped(const t_translation::terrain_code &terrain) const
Returns whether there is a defense cap associated to this terrain.
Definition: movetype.hpp:196
The basic "size" of the unit - flying, small land, large land, etc.
Definition: movetype.hpp:44
static const std::set< std::string > effects
The set of applicable effects for movement types.
Definition: movetype.hpp:340
void merge(const config &new_cfg, bool overwrite=true)
Merges the given config over the existing data, the config should have zero or more children named "m...
Definition: movetype.cpp:858
bool has_vision_data() const
Returns whether or not there are any vision-specific costs.
Definition: movetype.hpp:301
bool has_jamming_data() const
Returns whether or not there are any jamming-specific costs.
Definition: movetype.hpp:303
bool has_terrain_defense_caps(const std::set< t_translation::terrain_code > &ts) const
Returns whether or not there are any terrain caps with respect to a set of terrains.
Definition: movetype.cpp:850
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:288
int jamming_cost(const t_translation::terrain_code &terrain, bool slowed=false) const
Returns the cost to "jam" through the indicated terrain.
Definition: movetype.hpp:284
int vision_cost(const t_translation::terrain_code &terrain, bool slowed=false) const
Returns the cost to see through the indicated terrain.
Definition: movetype.hpp:281
int movement_cost(const t_translation::terrain_code &terrain, bool slowed=false) const
Returns the cost to move through the indicated terrain.
Definition: movetype.hpp:278
utils::string_map_res damage_table() const
Returns a map from damage types to resistances.
Definition: movetype.hpp:295
terrain_defense & get_defense()
Definition: movetype.hpp:263
static prefs & get()
bool empty() const
Definition: tstring.hpp:186
const std::string & str() const
Definition: tstring.hpp:190
const std::string & editor_group() const
Definition: terrain.hpp:152
bool has_default_base() const
Definition: terrain.hpp:177
const std::string & icon_image() const
Definition: terrain.hpp:44
const t_string & income_description() const
Definition: terrain.hpp:147
bool is_combined() const
True for instances created by the terrain_code(base, overlay) constructor.
Definition: terrain.hpp:167
const std::string & editor_image() const
Definition: terrain.hpp:47
bool is_nonnull() const
True if this object represents some sentinel values.
Definition: terrain.hpp:129
bool is_keep() const
Definition: terrain.hpp:143
bool is_castle() const
Definition: terrain.hpp:142
const std::string & id() const
Definition: terrain.hpp:52
const t_string & help_topic_text() const
Definition: terrain.hpp:51
bool is_village() const
Definition: terrain.hpp:141
const t_translation::ter_list & def_type() const
Definition: terrain.hpp:76
const t_translation::ter_list & mvt_type() const
The underlying type of the terrain.
Definition: terrain.hpp:75
int light_bonus(int base) const
Returns the light (lawful) bonus for this terrain when the time of day gives a base bonus.
Definition: terrain.hpp:132
const t_translation::ter_list & union_type() const
Definition: terrain.hpp:78
bool is_overlay() const
Definition: terrain.hpp:155
static bool is_indivisible(t_translation::terrain_code id, const t_translation::ter_list &underlying)
Returns true if a terrain has no underlying types other than itself, in respect of either union,...
Definition: terrain.hpp:100
const t_string & editor_name() const
Definition: terrain.hpp:49
int gives_healing() const
Definition: terrain.hpp:140
t_translation::terrain_code default_base() const
Overlay terrains defined by a [terrain_type] can declare a fallback base terrain, for use when the ov...
Definition: terrain.hpp:176
bool hide_in_editor() const
Definition: terrain.hpp:62
t_translation::terrain_code number() const
Definition: terrain.hpp:66
const t_string & plural_name() const
Definition: race.hpp:39
@ FEMALE
Definition: race.hpp:28
@ MALE
Definition: race.hpp:28
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1263
void build_unit_type(const unit_type &ut, unit_type::BUILD_STATUS status) const
Makes sure the provided unit_type is built to the specified level.
Definition: types.cpp:1255
A single unit type that the player may recruit.
Definition: types.hpp:43
bool can_advance() const
Definition: types.hpp:222
const std::vector< std::string > advances_from() const
A vector of unit_type ids that can advance to this unit_type.
Definition: types.cpp:652
std::string race_id() const
Returns the ID of this type's race without the need to build the type.
Definition: types.hpp:272
static std::string alignment_description(unit_alignments::type align, unit_race::GENDER gender=unit_race::MALE)
Implementation detail of unit_type::alignment_description.
Definition: types.cpp:839
const std::string & image() const
Definition: types.hpp:176
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:141
const std::vector< ability_metadata > & adv_abilities_metadata() const
Some extra abilities that may be gained through AMLA advancements.
Definition: types.hpp:220
const movetype & movement_type() const
Definition: types.hpp:189
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:762
const unit_race * race() const
Never returns nullptr, but may point to the null race.
Definition: types.hpp:277
int hitpoints() const
Definition: types.hpp:161
const unit_type & get_variation(const std::string &id) const
Definition: types.cpp:474
const_attack_itors attacks() const
Definition: types.cpp:543
const std::vector< std::string > & advances_to() const
A vector of unit_type ids that this unit_type can advance to.
Definition: types.hpp:115
bool has_gender_variation(const unit_race::GENDER gender) const
Definition: types.hpp:250
int movement() const
Definition: types.hpp:166
t_string unit_description() const
Definition: types.cpp:484
@ HELP_INDEXED
Definition: types.hpp:74
@ FULL
Definition: types.hpp:74
const std::vector< unit_race::GENDER > & genders() const
The returned vector will not be empty, provided this has been built to the HELP_INDEXED status.
Definition: types.hpp:249
std::vector< std::string > variations() const
Definition: types.cpp:745
const std::string & flag_rgb() const
Definition: types.cpp:720
int vision() const
Definition: types.hpp:167
std::vector< t_string > special_notes() const
Returns all notes that should be displayed in the help page for this type, including those found in a...
Definition: types.cpp:493
config::const_child_itors modification_advancements() const
Returns two iterators pointing to a range of AMLA configs.
Definition: types.hpp:120
int cost() const
Definition: types.hpp:172
const unit_type & get_gender_unit_type(std::string gender) const
Returns a gendered variant of this unit_type.
Definition: types.cpp:453
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:577
const std::string & big_profile() const
Definition: types.hpp:179
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:138
config::const_child_itors possible_traits() const
Definition: types.hpp:231
int level() const
Definition: types.hpp:164
unit_alignments::type alignment() const
Definition: types.hpp:193
const std::string & small_profile() const
Definition: types.hpp:178
const std::vector< ability_metadata > & abilities_metadata() const
Definition: types.hpp:217
const config & get_cfg() const
Definition: types.hpp:281
unsigned int num_traits() const
Definition: types.hpp:135
int jamming() const
Definition: types.hpp:170
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1023
static std::string _(const char *str)
Definition: gettext.hpp:93
#define WRN_HP
static lg::log_domain log_help("help")
symbol_table string_table
Definition: language.cpp:64
Standard logging facilities (interface).
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
int pango_line_width(const std::string &line, int font_size, font::pango_text::FONT_STYLE font_style=font::pango_text::STYLE_NORMAL)
Determine the width of a line of text given a certain font size.
const std::string nbsp
Definition: constants.cpp:40
const std::string unicode_bullet
Definition: constants.cpp:47
const std::string unicode_figure_dash
Definition: constants.cpp:45
const std::string weapon_numbers_sep
Definition: constants.cpp:49
const std::string unicode_minus
Definition: constants.cpp:42
const bool & debug
Definition: game_config.cpp:92
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....
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:27
@ FULL_DESCRIPTION
Definition: help_impl.hpp:243
std::string bold(const std::string &s)
Definition: help_impl.hpp:379
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:373
static std::string print_behavior_description(ter_iter start, ter_iter end, const std::shared_ptr< terrain_type_data > &tdata, bool first_level=true, bool begin_best=true)
t_translation::ter_list::const_iterator ter_iter
const std::string unit_prefix
Definition: help_impl.cpp:90
const std::string variation_prefix
Definition: help_impl.cpp:95
UNIT_DESCRIPTION_TYPE description_type(const unit_type &type)
Return the type of description that should be shown for a unit of the given kind.
Definition: help_impl.cpp:1189
const std::string ability_prefix
Definition: help_impl.cpp:96
std::pair< std::string, std::string > trait_data
const int normal_font_size
Definition: help_impl.cpp:82
const std::string terrain_prefix
Definition: help_impl.cpp:91
const std::string unknown_unit_topic
Definition: help_impl.cpp:89
std::shared_ptr< terrain_type_data > load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1863
static std::string best_str(bool best)
static void print_trait_list(std::stringstream &ss, const std::vector< trait_data > &l)
logger & info()
Definition: log.cpp:316
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
const terrain_code MINUS
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
const terrain_code BASE
const ter_match ALL_OFF_MAP
const terrain_code PLUS
const terrain_code FOGGED
static std::string gettext(const char *str)
Definition: gettext.hpp:60
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
Definition: gettext.cpp:519
std::string resistance_color(const int resistance)
Maps resistance <= -60 (resistance value <= -60%) to intense red.
Definition: helper.cpp:48
std::map< std::string, t_string, res_compare > string_map_res
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:432
int get_pixel_scale()
Get the current active pixel scale multiplier.
Definition: video.cpp:481
Transitional API for porting SDL_ttf-based code to Pango.
std::string to_hex_string() const
Returns the stored color in rrggbb hex format.
Definition: color.cpp:88
bool operator<(const terrain_movement_info &other) const
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
static map_location::DIRECTION s
unit_type_data unit_types
Definition: types.cpp:1482