The Battle for Wesnoth  1.19.0-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 "preferences/game.hpp" // for encountered_terrains, etc
24 #include "gettext.hpp" // for _, gettext, N_
25 #include "language.hpp" // for string_table, symbol_table
26 #include "log.hpp" // for LOG_STREAM, logger, etc
27 #include "movetype.hpp" // for movetype, movetype::effects, 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 "video.hpp" // for game_canvas_size
36 
37 #include <optional>
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 "<format>color='" + color_policy + "' text='" + lang_policy + "'</format>";
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  std::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  const terrain_type tt = tdata->get_terrain_info(*i);
93  if (!tt.editor_name().empty())
94  names.push_back(tt.editor_name());
95  }
96 
97  if (names.empty()) return "";
98  if (names.size() == 1) return names.at(0);
99 
100  ss << best_str(best) << " ";
101  if (!first_level) ss << "( ";
102  ss << names.at(0);
103 
104  for (std::size_t i = 1; i < names.size(); i++) {
105  ss << ", " << names.at(i);
106  }
107 
108  if (!first_level) ss << " )";
109  } else {
110  std::vector<std::string> names;
111  for (ter_iter i = *last_change_pos+1; i != end; ++i) {
112  const terrain_type tt = tdata->get_terrain_info(*i);
113  if (!tt.editor_name().empty())
114  names.push_back(tt.editor_name());
115  }
116 
117  if (names.empty()) { //This alias list is apparently padded with junk at the end, so truncate it without adding more parens
118  return print_behavior_description(start, *last_change_pos, tdata, first_level, begin_best);
119  }
120 
121  ss << best_str(best) << " ";
122  if (!first_level) ss << "( ";
123  ss << print_behavior_description(start, *last_change_pos-1, tdata, false, begin_best);
124  // Printed the (parenthesized) leading part from before the change, now print the remaining names in this group.
125  for (const std::string & s : names) {
126  ss << ", " << s;
127  }
128  if (!first_level) ss << " )";
129  }
130  return ss.str();
131 }
132 
134  std::stringstream ss;
135 
136  if (!type_.icon_image().empty())
137  ss << "<img>src='images/buttons/icon-base-32.png~RC(magenta>" << type_.id()
138  << ")~BLIT("<< "terrain/" << type_.icon_image() << "_30.png)" << "'</img> ";
139 
140  if (!type_.editor_image().empty())
141  ss << "<img>src='" << type_.editor_image() << "'</img> ";
142 
143  if (!type_.help_topic_text().empty())
144  ss << "\n\n" << type_.help_topic_text().str() << "\n";
145  else
146  ss << "\n";
147 
148  std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data();
149 
150  if (!tdata) {
151  WRN_HP << "When building terrain help topics, we couldn't acquire any terrain types data";
152  return ss.str();
153  }
154 
155  // Special notes are generated from the terrain's properties - at the moment there's no way for WML authors
156  // to add their own via a [special_note] tag.
157  std::vector<std::string> special_notes;
158 
159  if(type_.is_village()) {
160  special_notes.push_back(_("Villages allow any unit stationed therein to heal, or to be cured of poison."));
161  } else if(type_.gives_healing() > 0) {
162  auto symbols = utils::string_map{{"amount", std::to_string(type_.gives_healing())}};
163  // TRANSLATORS: special note for terrains such as the oasis; the only terrain in core with this property heals 8 hp just like a village.
164  // 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.
165  auto message = VNGETTEXT("This terrain allows units to be cured of poison, or to heal a single hitpoint.",
166  "This terrain allows units to heal $amount hitpoints, or to be cured of poison, as if stationed in a village.",
167  type_.gives_healing(), symbols);
168  special_notes.push_back(std::move(message));
169  }
170 
171  if(type_.is_castle()) {
172  special_notes.push_back(_("This terrain is a castle — units can be recruited onto it from a connected keep."));
173  }
174  if(type_.is_keep() && type_.is_castle()) {
175  // TRANSLATORS: The "this terrain is a castle" note will also be shown directly above this one.
176  special_notes.push_back(_("This terrain is a keep — a leader can recruit from this hex onto connected castle hexes."));
177  } else if(type_.is_keep() && !type_.is_castle()) {
178  // TRANSLATORS: Special note for a terrain, but none of the terrains in mainline do this.
179  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."));
180  }
181 
182  if(!special_notes.empty()) {
183  ss << "\n" << _("Special Notes:") << '\n';
184  for(const auto& note : special_notes) {
185  ss << font::unicode_bullet << " " << note << '\n';
186  }
187  }
188 
189  // Almost all terrains will show the data in this conditional block. The ones that don't are the
190  // archetypes used in [movetype]'s subtags such as [movement_costs].
191  if (!type_.is_indivisible()) {
192  ss << "\n" << _("Base Terrain: ");
193 
194  bool first = true;
195  for (const auto& underlying_terrain : type_.union_type()) {
196  const terrain_type& base = tdata->get_terrain_info(underlying_terrain);
197 
198  if (base.editor_name().empty()) continue;
199 
200  if (!first) {
201  ss << ", ";
202  } else {
203  first = false;
204  }
205 
206  ss << make_link(base.editor_name(), ".." + terrain_prefix + base.id());
207  }
208 
209  ss << "\n";
210 
211  const t_translation::ter_list& underlying_mvt_terrains = type_.mvt_type();
212  ss << "\n" << _("Movement properties: ");
213  ss << print_behavior_description(underlying_mvt_terrains.begin(), underlying_mvt_terrains.end(), tdata) << "\n";
214 
215  const t_translation::ter_list& underlying_def_terrains = type_.def_type();
216  ss << "\n" << _("Defense properties: ");
217  ss << print_behavior_description(underlying_def_terrains.begin(), underlying_def_terrains.end(), tdata) << "\n";
218  }
219 
220  if (game_config::debug) {
221 
222  ss << "\n";
223  ss << "ID: " << type_.id() << "\n";
224 
225  ss << "Village: " << (type_.is_village() ? "Yes" : "No") << "\n";
226  ss << "Gives Healing: " << type_.gives_healing() << "\n";
227 
228  ss << "Keep: " << (type_.is_keep() ? "Yes" : "No") << "\n";
229  ss << "Castle: " << (type_.is_castle() ? "Yes" : "No") << "\n";
230 
231  ss << "Overlay: " << (type_.is_overlay() ? "Yes" : "No") << "\n";
232  ss << "Combined: " << (type_.is_combined() ? "Yes" : "No") << "\n";
233  ss << "Nonnull: " << (type_.is_nonnull() ? "Yes" : "No") << "\n";
234 
235  ss << "Terrain string: " << type_.number() << "\n";
236 
237  ss << "Hide in Editor: " << (type_.hide_in_editor() ? "Yes" : "No") << "\n";
238  ss << "Editor Group: " << type_.editor_group() << "\n";
239 
240  ss << "Light Bonus: " << type_.light_bonus(0) << "\n";
241 
242  ss << type_.income_description();
243 
244  if (type_.editor_image().empty()) { // Note: this is purely temporary to help make a different help entry
245  ss << "\nEditor Image: Empty\n";
246  } else {
247  ss << "\nEditor Image: " << type_.editor_image() << "\n";
248  }
249 
250  const t_translation::ter_list& underlying_mvt_terrains = tdata->underlying_mvt_terrain(type_.number());
251  ss << "\nDebug Mvt Description String:";
252  for (const t_translation::terrain_code & t : underlying_mvt_terrains) {
253  ss << " " << t;
254  }
255 
256  const t_translation::ter_list& underlying_def_terrains = tdata->underlying_def_terrain(type_.number());
257  ss << "\nDebug Def Description String:";
258  for (const t_translation::terrain_code & t : underlying_def_terrains) {
259  ss << " " << t;
260  }
261 
262  }
263 
264  return ss.str();
265 }
266 
267 
268 //Typedef to help with formatting list of traits
269 // Maps localized trait name to trait help topic ID
270 typedef std::pair<std::string, std::string> trait_data;
271 
272 //Helper function for printing a list of trait data
273 static void print_trait_list(std::stringstream & ss, const std::vector<trait_data> & l)
274 {
275  std::size_t i = 0;
276  ss << make_link(l[i].first, l[i].second);
277 
278  // This doesn't skip traits with empty names
279  for(i++; i < l.size(); i++) {
280  ss << ", " << make_link(l[i].first,l[i].second);
281  }
282 }
283 
284 std::string unit_topic_generator::operator()() const {
285  // Force the lazy loading to build this unit.
287 
288  std::stringstream ss;
289  std::string clear_stringstream;
290  const std::string detailed_description = type_.unit_description();
293 
294  const int screen_width = video::game_canvas_size().x;
295 
296  ss << _("Level") << " " << type_.level();
297  ss << "\n\n";
298 
299  ss << "<img>src='" << male_type.image();
300  ss << "~RC(" << male_type.flag_rgb() << ">red)";
301  if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
302  ss << "' box='no'</img> ";
303 
304  if (female_type.image() != male_type.image()) {
305  ss << "<img>src='" << female_type.image();
306  ss << "~RC(" << female_type.flag_rgb() << ">red)";
307  if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
308  ss << "' box='no'</img> ";
309  }
310 
311  const std::string &male_portrait = male_type.small_profile().empty() ?
312  male_type.big_profile() : male_type.small_profile();
313  const std::string &female_portrait = female_type.small_profile().empty() ?
314  female_type.big_profile() : female_type.small_profile();
315 
316  const bool has_male_portrait = !male_portrait.empty() && male_portrait != male_type.image() && male_portrait != "unit_image";
317  const bool has_female_portrait = !female_portrait.empty() && female_portrait != male_portrait && female_portrait != female_type.image() && female_portrait != "unit_image";
318 
319  int sz = (has_male_portrait && has_female_portrait ? 300 : 400);
320  if (screen_width <= 1366) {
321  sz = (has_male_portrait && has_female_portrait ? 200 : 300);
322  } else if (screen_width >= 1920) {
323  sz = 400;
324  }
325 
326  // without this, scaling down (SCALE_INTO below) and then scaling back up due to the pixel multiplier leads to ugly results
327  // can't use the preferences value since it may be different than the actual value
328  sz *= video::get_pixel_scale();
329 
330  // TODO: figure out why the second checks don't match but the last does
331  if (has_male_portrait) {
332  ss << "<img>src='" << male_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
333  }
334 
335 
336  if (has_female_portrait) {
337  ss << "<img>src='" << female_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
338  }
339 
340  ss << "\n\n\n";
341 
342  // Print cross-references to units that this unit advances from/to.
343  // Cross reference to the topics containing information about those units.
344  const bool first_reverse_value = true;
345  bool reverse = first_reverse_value;
346  if (variation_.empty()) {
347  do {
348  std::vector<std::string> adv_units =
349  reverse ? type_.advances_from() : type_.advances_to();
350  bool first = true;
351 
352  for (const std::string &adv : adv_units) {
354  if (!type || type->hide_help()) {
355  continue;
356  }
357 
358  if (first) {
359  if (reverse) {
360  ss << _("Advances from: ");
361  } else {
362  ss << _("Advances to: ");
363  }
364  first = false;
365  } else {
366  ss << ", ";
367  }
368 
369  std::string lang_unit = type->type_name();
370  std::string ref_id;
372  const std::string section_prefix = type->show_variations_in_help() ? ".." : "";
373  ref_id = section_prefix + unit_prefix + type->id();
374  } else {
375  ref_id = unknown_unit_topic;
376  lang_unit += " (?)";
377  }
378  ss << make_link(lang_unit, ref_id);
379  }
380  if (!first) {
381  ss << "\n";
382  }
383 
384  reverse = !reverse; //switch direction
385  } while(reverse != first_reverse_value); // don't restart
386  }
387 
388  const unit_type* parent = variation_.empty() ? &type_ :
390  if (!variation_.empty()) {
391  ss << _("Base unit: ") << make_link(parent->type_name(), ".." + unit_prefix + type_.id()) << "\n";
392  } else {
393  bool first = true;
394  for (const std::string& base_id : utils::split(type_.get_cfg()["base_ids"])) {
395  if (first) {
396  ss << _("Base units: ");
397  first = false;
398  }
399  const unit_type* base_type = unit_types.find(base_id, unit_type::HELP_INDEXED);
400  const std::string section_prefix = base_type->show_variations_in_help() ? ".." : "";
401  ss << make_link(base_type->type_name(), section_prefix + unit_prefix + base_id) << "\n";
402  }
403  }
404 
405  bool first = true;
406  for (const std::string &var_id : parent->variations()) {
407  const unit_type &type = parent->get_variation(var_id);
408 
409  if(type.hide_help()) {
410  continue;
411  }
412 
413  if (first) {
414  ss << _("Variations: ");
415  first = false;
416  } else {
417  ss << ", ";
418  }
419 
420  std::string ref_id;
421 
422  std::string var_name = type.variation_name();
424  ref_id = variation_prefix + type.id() + "_" + var_id;
425  } else {
426  ref_id = unknown_unit_topic;
427  var_name += " (?)";
428  }
429 
430  ss << make_link(var_name, ref_id);
431  }
432  ss << "\n"; //added even if empty, to avoid shifting
433 
434  // Print the race of the unit, cross-reference it to the respective topic.
435  const std::string race_id = type_.race_id();
436  std::string race_name = type_.race()->plural_name();
437  if (race_name.empty()) {
438  race_name = _ ("race^Miscellaneous");
439  }
440  ss << _("Race: ");
441  ss << make_link(race_name, "..race_" + race_id);
442  ss << "\n\n";
443 
444  // Print the possible traits of the unit, cross-reference them
445  // to their respective topics.
447  std::vector<trait_data> must_have_traits;
448  std::vector<trait_data> random_traits;
449  int must_have_nameless_traits = 0;
450 
451  for(const config& trait : traits) {
452  const std::string& male_name = trait["male_name"].str();
453  const std::string& female_name = trait["female_name"].str();
454  std::string trait_name;
455  if (type_.has_gender_variation(unit_race::MALE) && ! male_name.empty())
456  trait_name = male_name;
457  else if (type_.has_gender_variation(unit_race::FEMALE) && ! female_name.empty())
458  trait_name = female_name;
459  else if (! trait["name"].str().empty())
460  trait_name = trait["name"].str();
461  else
462  continue; // Hidden trait
463 
464  std::string lang_trait_name = translation::gettext(trait_name.c_str());
465  if (lang_trait_name.empty() && trait["availability"].str() == "musthave") {
466  ++must_have_nameless_traits;
467  continue;
468  }
469  const std::string ref_id = "traits_"+trait["id"].str();
470  ((trait["availability"].str() == "musthave") ? must_have_traits : random_traits).emplace_back(lang_trait_name, ref_id);
471  }
472 
473  bool line1 = !must_have_traits.empty();
474  bool line2 = !random_traits.empty() && type_.num_traits() > must_have_traits.size();
475 
476  if (line1) {
477  std::string traits_label = _("Traits");
478  ss << traits_label;
479  if (line2) {
480  std::stringstream must_have_count;
481  must_have_count << " (" << must_have_traits.size() << ") : ";
482  std::stringstream random_count;
483  random_count << " (" << (type_.num_traits() - must_have_traits.size() - must_have_nameless_traits) << ") : ";
484 
485  int second_line_whitespace = font::pango_line_width(traits_label+must_have_count.str(), normal_font_size)
486  - font::pango_line_width(random_count.str(), normal_font_size);
487  // This ensures that the second line is justified so that the ':' characters are aligned.
488 
489  ss << must_have_count.str();
490  print_trait_list(ss, must_have_traits);
491  ss << "\n" << jump(second_line_whitespace) << random_count.str();
492  print_trait_list(ss, random_traits);
493  } else {
494  ss << ": ";
495  print_trait_list(ss, must_have_traits);
496  }
497  ss << "\n\n";
498  } else {
499  if (line2) {
500  ss << _("Traits") << " (" << (type_.num_traits() - must_have_nameless_traits) << ") : ";
501  print_trait_list(ss, random_traits);
502  ss << "\n\n";
503  }
504  }
505  }
506 
507  // Print the abilities the units has, cross-reference them
508  // to their respective topics. TODO: Update this according to musthave trait effects, similar to movetype below
509  if(!type_.abilities_metadata().empty()) {
510  ss << _("Abilities: ");
511 
512  bool start = true;
513 
514  for(auto iter = type_.abilities_metadata().begin(); iter != type_.abilities_metadata().end(); ++iter) {
515  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
516 
517  if(iter->name.empty()) {
518  continue;
519  }
520 
521  if(!start) {
522  ss << ", ";
523  } else {
524  start = false;
525  }
526 
527  std::string lang_ability = translation::gettext(iter->name.c_str());
528  ss << make_link(lang_ability, ref_id);
529  }
530 
531  ss << "\n\n";
532  }
533 
534  // Print the extra AMLA upgrade abilities, cross-reference them to their respective topics.
535  if(!type_.adv_abilities_metadata().empty()) {
536  ss << _("Ability Upgrades: ");
537 
538  bool start = true;
539 
540  for(auto iter = type_.adv_abilities_metadata().begin(); iter != type_.adv_abilities_metadata().end(); ++iter) {
541  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
542 
543  if(iter->name.empty()) {
544  continue;
545  }
546 
547  if(!start) {
548  ss << ", ";
549  } else {
550  start = false;
551  }
552 
553  std::string lang_ability = translation::gettext(iter->name.c_str());
554  ss << make_link(lang_ability, ref_id);
555  }
556 
557  ss << "\n\n";
558  }
559 
560  // Print some basic information such as HP and movement points.
561  // TODO: Make this info update according to musthave traits, similar to movetype below.
562 
563  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
564  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
565  // unpleasant line breaks (issue #3256).
566  ss << _("HP:") << font::nbsp << type_.hitpoints() << jump(30)
567  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
568  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
569  // unpleasant line breaks (issue #3256).
570  << _("Moves:") << font::nbsp << type_.movement() << jump(30);
571  if (type_.vision() != type_.movement()) {
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 << _("Vision:") << font::nbsp << type_.vision() << jump(30);
576  }
577  if (type_.jamming() > 0) {
578  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
579  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
580  // unpleasant line breaks (issue #3256).
581  ss << _("Jamming:") << font::nbsp << type_.jamming() << jump(30);
582  }
583  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
584  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
585  // unpleasant line breaks (issue #3256).
586  ss << _("Cost:") << font::nbsp << type_.cost() << jump(30)
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  << _("Alignment:") << font::nbsp
591  << make_link(type_.alignment_description(type_.alignment(), type_.genders().front()), "time_of_day")
592  << jump(30);
594  // TRANSLATORS: This string is used in the help page of a single unit. It uses
595  // non-breaking spaces to prevent unpleasant line breaks (issue #3256). In the
596  // translation use non-breaking spaces as appropriate for the target language.
597  ss << _("Required\u00a0XP:") << font::nbsp << type_.experience_needed();
598  }
599 
600  // Print the detailed description about the unit.
601  ss << "\n\n" << detailed_description;
602  if(const auto notes = type_.special_notes(); !notes.empty()) {
603  ss << "\n\n" << _("Special Notes:") << '\n';
604  for(const auto& note : notes) {
605  ss << font::unicode_bullet << " " << note << '\n';
606  }
607  }
608 
609  // Padding for range and damage type icons
610  const auto padding = 4; // matches the alignment of the terrain rows
611 
612  // Print the different attacks a unit has, if it has any.
613  if (!type_.attacks().empty()) {
614  // Print headers for the table.
615  ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
616  << "'</header>\n\n";
617  table_spec table;
618 
619  std::vector<item> first_row;
620  // Dummy element, icons are below.
621  first_row.push_back(item("", 0));
622  push_header(first_row, _("unit help^Name"));
623  push_header(first_row, _("Strikes"));
624  push_header(first_row, _("Range"));
625  push_header(first_row, _("Type"));
626  push_header(first_row, _("Special"));
627  table.push_back(first_row);
628  // Print information about every attack.
629  for(const attack_type& attack : type_.attacks()) {
630  std::string lang_weapon = attack.name();
631  std::string lang_type = string_table["type_" + attack.type()];
632  std::vector<item> row;
633  std::stringstream attack_ss;
634 
635  // Attack icon
636  attack_ss << "<img>src='" << attack.icon() << "'</img>";
637  row.emplace_back(attack_ss.str(),image_width(attack.icon()));
638  attack_ss.str(clear_stringstream);
639 
640  // Attack name
641  push_tab_pair(row, lang_weapon);
642 
643  // damage x strikes
644  attack_ss << attack.damage() << font::weapon_numbers_sep << attack.num_attacks()
645  << " " << attack.accuracy_parry_description();
646  push_tab_pair(row, attack_ss.str());
647  attack_ss.str(clear_stringstream);
648 
649  // Range, with icon
650  const std::string range_icon = "icons/profiles/" + attack.range() + "_attack.png~SCALE_INTO(16,16)";
651  if (attack.min_range() > 1 || attack.max_range() > 1) {
652  attack_ss << attack.min_range() << "-" << attack.max_range() << ' ';
653  }
654  attack_ss << string_table["range_" + attack.range()];
655  push_tab_pair(row, attack_ss.str(), range_icon, padding);
656  attack_ss.str(clear_stringstream);
657 
658  // Damage type, with icon
659  const std::string type_icon = "icons/profiles/" + attack.type() + ".png~SCALE_INTO(16,16)";
660  push_tab_pair(row, lang_type, type_icon, padding);
661 
662  // Show this attack's special, if it has any. Cross
663  // reference it to the section describing the special.
664  std::vector<std::pair<t_string, t_string>> specials = attack.special_tooltips();
665  if (!specials.empty()) {
666  std::string lang_special = "";
667  const std::size_t specials_size = specials.size();
668  for (std::size_t i = 0; i != specials_size; ++i) {
669  const std::string ref_id = std::string("weaponspecial_")
670  + specials[i].first.base_str();
671  lang_special = (specials[i].first);
672  attack_ss << make_link(lang_special, ref_id);
673  if (i+1 != specials_size) {
674  attack_ss << ", "; //comma placed before next special
675  }
676  }
677  row.emplace_back(attack_ss.str(), font::pango_line_width(lang_special, normal_font_size));
678  }
679  table.push_back(row);
680  }
681  ss << generate_table(table);
682  }
683 
684  // Generate the movement type of the unit, with resistance, defense, movement, jamming and vision data updated according to any 'musthave' traits which always apply
685  movetype movement_type = type_.movement_type();
687  if (!traits.empty() && type_.num_traits() > 0) {
688  for (const config & t : traits) {
689  if (t["availability"].str() == "musthave") {
690  for (const config & effect : t.child_range("effect")) {
691  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.
692  && movetype::effects.find(effect["apply_to"].str()) != movetype::effects.end()) {
693  movement_type.merge(effect, effect["replace"].to_bool());
694  }
695  }
696  }
697  }
698  }
699 
700  // Print the resistance table of the unit.
701  ss << "\n\n<header>text='" << escape(_("Resistances"))
702  << "'</header>\n\n";
703  table_spec resistance_table;
704  std::vector<item> first_res_row;
705  push_header(first_res_row, _("Attack Type"));
706  push_header(first_res_row, _("Resistance"));
707  resistance_table.push_back(first_res_row);
708  utils::string_map_res dam_tab = movement_type.damage_table();
709  for(std::pair<std::string, std::string> dam_it : dam_tab) {
710  std::vector<item> row;
711  int resistance = 100;
712  try {
713  resistance -= std::stoi(dam_it.second);
714  } catch(std::invalid_argument&) {}
715  std::string resist = std::to_string(resistance) + '%';
716  const std::size_t pos = resist.find('-');
717  if (pos != std::string::npos) {
718  resist.replace(pos, 1, font::unicode_minus);
719  }
720  std::string color = unit_helper::resistance_color(resistance);
721  const std::string lang_type = string_table["type_" + dam_it.first];
722  const std::string type_icon = "icons/profiles/" + dam_it.first + ".png~SCALE_INTO(16,16)";
723  push_tab_pair(row, lang_type, type_icon, padding);
724  std::stringstream str;
725  str << "<format>color=\"" << color << "\" text='"<< resist << "'</format>";
726  const std::string markup = str.str();
727  str.str(clear_stringstream);
728  str << resist;
729  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
730  resistance_table.push_back(row);
731  }
732  ss << generate_table(resistance_table);
733 
734  if (std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data()) {
735  // Print the terrain modifier table of the unit.
736  ss << "\n\n<header>text='" << escape(_("Terrain Modifiers"))
737  << "'</header>\n\n";
738  std::vector<item> first_row;
739  table_spec table;
740  push_header(first_row, _("Terrain"));
741  push_header(first_row, _("Defense"));
742  push_header(first_row, _("Movement Cost"));
743 
744  const bool has_terrain_defense_caps = movement_type.has_terrain_defense_caps(preferences::encountered_terrains());
745  if (has_terrain_defense_caps) {
746  push_header(first_row, _("Defense Cap"));
747  }
748 
749  const bool has_vision = type_.movement_type().has_vision_data();
750  if (has_vision) {
751  push_header(first_row, _("Vision Cost"));
752  }
753  const bool has_jamming = type_.movement_type().has_jamming_data();
754  if (has_jamming) {
755  push_header(first_row, _("Jamming Cost"));
756  }
757 
758  table.push_back(first_row);
759 
760  std::set<terrain_movement_info> terrain_moves;
761 
764  continue;
765  }
766  const terrain_type& info = tdata->get_terrain_info(terrain);
767  const int moves = movement_type.movement_cost(terrain);
768  const bool cannot_move = moves > type_.movement();
769  if (cannot_move && info.hide_if_impassable()) {
770  continue;
771  }
772 
773  if (info.is_indivisible() && info.is_nonnull()) {
774  terrain_movement_info movement_info =
775  {
776  info.name(),
777  info.id(),
778  100 - movement_type.defense_modifier(terrain),
779  moves,
780  movement_type.vision_cost(terrain),
781  movement_type.jamming_cost(terrain),
782  movement_type.get_defense().capped(terrain)
783  };
784 
785  terrain_moves.insert(movement_info);
786  }
787  }
788 
789  for(const terrain_movement_info &m : terrain_moves)
790  {
791  std::vector<item> row;
792 
793  bool high_res = false;
794  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
795  const std::string terrain_image = "icons/terrain/terrain_type_" + m.id + (high_res ? "_30.png" : ".png");
796 
797  const std::string final_image = tc_base + "~RC(magenta>" + m.id + ")~BLIT(" + terrain_image + ")";
798 
799  row.emplace_back("<img>src='" + final_image + "'</img> " +
800  make_link(m.name, "..terrain_" + m.id),
801  font::pango_line_width(m.name, normal_font_size) + (high_res ? 32 : 16) );
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 << "<format>color='" << color << "' text='"<< m.defense << "%'</format>";
810  str_unformatted << m.defense << "%";
811  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
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 << "<format>color='" << movement_color << "' text='";
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() << "'</format>";
840  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
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 << "<format>color='"<< color <<"' text='" << m.defense << "%'</format>";
848  str_unformatted << m.defense << "%";
849  } else {
850  str << "<format>color=white text='" << font::unicode_figure_dash << "'</format>";
851  str_unformatted << font::unicode_figure_dash;
852  }
853  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
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 << "<format>color='" << vision_color << "' text='";
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() << "'</format>";
884  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
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 << "<format>color='" << jamming_color << "' text='";
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() << "'</format>";
915  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
916  }
917 
918  table.push_back(row);
919  }
920 
921  ss << generate_table(table);
922  } else {
923  WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.";
924  }
925  return ss.str();
926 }
927 
928 void unit_topic_generator::push_header(std::vector< item > &row, const std::string& name) const {
930 }
931 
932 } // end namespace help
double t
Definition: astarsearch.cpp:63
std::vector< std::string > names
Definition: build_info.cpp:66
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:283
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:197
The basic "size" of the unit - flying, small land, large land, etc.
Definition: movetype.hpp:45
static const std::set< std::string > effects
The set of applicable effects for movement types.
Definition: movetype.hpp:346
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:874
bool has_vision_data() const
Returns whether or not there are any vision-specific costs.
Definition: movetype.hpp:307
bool has_jamming_data() const
Returns whether or not there are any jamming-specific costs.
Definition: movetype.hpp:309
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:866
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:291
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:287
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:284
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:281
utils::string_map_res damage_table() const
Returns a map from attack types to resistances.
Definition: movetype.hpp:301
terrain_defense & get_defense()
Definition: movetype.hpp:266
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
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
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:38
@ FEMALE
Definition: race.hpp:27
@ MALE
Definition: race.hpp:27
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:1267
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:1259
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 VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:968
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:91
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
Definition: help.cpp:53
@ FULL_DESCRIPTION
Definition: help_impl.hpp:242
std::string bold(const std::string &s)
Definition: help_impl.hpp:404
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:384
std::string escape(const std::string &s)
Prepend all chars with meaning inside attributes with a backslash.
Definition: help_impl.cpp:1689
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:87
unsigned image_width(const std::string &filename)
Definition: help_impl.cpp:1622
const std::string variation_prefix
Definition: help_impl.cpp:92
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:1180
const std::string ability_prefix
Definition: help_impl.cpp:93
std::vector< std::vector< help::item > > table_spec
Definition: help_impl.hpp:414
void push_tab_pair(std::vector< help::item > &v, const std::string &s, const std::optional< std::string > &image, unsigned padding)
Definition: help_impl.cpp:1632
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:412
std::pair< std::string, std::string > trait_data
const int normal_font_size
Definition: help_impl.cpp:79
std::string generate_table(const table_spec &tab, const unsigned int spacing)
Definition: help_impl.cpp:1646
const std::string terrain_prefix
Definition: help_impl.cpp:88
const std::string unknown_unit_topic
Definition: help_impl.cpp:86
std::shared_ptr< terrain_type_data > load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1695
static std::string best_str(bool best)
std::string jump(const unsigned amount)
Definition: help_impl.hpp:397
static void print_trait_list(std::stringstream &ss, const std::vector< trait_data > &l)
logger & info()
Definition: log.cpp:314
std::set< t_translation::terrain_code > & encountered_terrains()
Definition: game.cpp:918
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 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::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:434
int get_pixel_scale()
Get the current active pixel scale multiplier.
Definition: video.cpp:483
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:78
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:1486