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/preferences.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  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> ";
147 
148  if (!type_.help_topic_text().empty())
149  ss << "\n\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" << _("Special Notes:") << '\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  ss << "\n\n";
313 
314  ss << "<img>src='" << male_type.image();
315  ss << "~RC(" << male_type.flag_rgb() << ">red)";
316  if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
317  ss << "' box='no'</img> ";
318 
319  if (female_type.image() != male_type.image()) {
320  ss << "<img>src='" << female_type.image();
321  ss << "~RC(" << female_type.flag_rgb() << ">red)";
322  if (screen_width >= 1200) ss << "~SCALE_SHARP(200%,200%)";
323  ss << "' box='no'</img> ";
324  }
325 
326  const std::string &male_portrait = male_type.small_profile().empty() ?
327  male_type.big_profile() : male_type.small_profile();
328  const std::string &female_portrait = female_type.small_profile().empty() ?
329  female_type.big_profile() : female_type.small_profile();
330 
331  const bool has_male_portrait = !male_portrait.empty() && male_portrait != male_type.image() && male_portrait != "unit_image";
332  const bool has_female_portrait = !female_portrait.empty() && female_portrait != male_portrait && female_portrait != female_type.image() && female_portrait != "unit_image";
333 
334  int sz = (has_male_portrait && has_female_portrait ? 300 : 400);
335  if (screen_width <= 1366) {
336  sz = (has_male_portrait && has_female_portrait ? 200 : 300);
337  } else if (screen_width >= 1920) {
338  sz = 400;
339  }
340 
341  // without this, scaling down (SCALE_INTO below) and then scaling back up due to the pixel multiplier leads to ugly results
342  // can't use the preferences value since it may be different than the actual value
343  sz *= video::get_pixel_scale();
344 
345  // TODO: figure out why the second checks don't match but the last does
346  if (has_male_portrait) {
347  ss << "<img>src='" << male_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
348  }
349 
350 
351  if (has_female_portrait) {
352  ss << "<img>src='" << female_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
353  }
354 
355  ss << "\n\n\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  ss << "\n"; //added even if empty, to avoid shifting
448 
449  // Print the race of the unit, cross-reference it to the respective topic.
450  const std::string race_id = type_.race_id();
451  std::string race_name = type_.race()->plural_name();
452  if (race_name.empty()) {
453  race_name = _ ("race^Miscellaneous");
454  }
455  ss << _("Race: ");
456  ss << make_link(race_name, "..race_" + race_id);
457  ss << "\n\n";
458 
459  // Print the possible traits of the unit, cross-reference them
460  // to their respective topics.
462  std::vector<trait_data> must_have_traits;
463  std::vector<trait_data> random_traits;
464  int must_have_nameless_traits = 0;
465 
466  for(const config& trait : traits) {
467  const std::string& male_name = trait["male_name"].str();
468  const std::string& female_name = trait["female_name"].str();
469  std::string trait_name;
470  if (type_.has_gender_variation(unit_race::MALE) && ! male_name.empty())
471  trait_name = male_name;
472  else if (type_.has_gender_variation(unit_race::FEMALE) && ! female_name.empty())
473  trait_name = female_name;
474  else if (! trait["name"].str().empty())
475  trait_name = trait["name"].str();
476  else
477  continue; // Hidden trait
478 
479  std::string lang_trait_name = translation::gettext(trait_name.c_str());
480  if (lang_trait_name.empty() && trait["availability"].str() == "musthave") {
481  ++must_have_nameless_traits;
482  continue;
483  }
484  const std::string ref_id = "traits_"+trait["id"].str();
485  ((trait["availability"].str() == "musthave") ? must_have_traits : random_traits).emplace_back(lang_trait_name, ref_id);
486  }
487 
488  bool line1 = !must_have_traits.empty();
489  bool line2 = !random_traits.empty() && type_.num_traits() > must_have_traits.size();
490 
491  if (line1) {
492  std::string traits_label = _("Traits");
493  ss << traits_label;
494  if (line2) {
495  std::stringstream must_have_count;
496  must_have_count << " (" << must_have_traits.size() << ") : ";
497  std::stringstream random_count;
498  random_count << " (" << (type_.num_traits() - must_have_traits.size() - must_have_nameless_traits) << ") : ";
499 
500  int second_line_whitespace = font::pango_line_width(traits_label+must_have_count.str(), normal_font_size)
501  - font::pango_line_width(random_count.str(), normal_font_size);
502  // This ensures that the second line is justified so that the ':' characters are aligned.
503 
504  ss << must_have_count.str();
505  print_trait_list(ss, must_have_traits);
506  ss << "\n" << jump(second_line_whitespace) << random_count.str();
507  print_trait_list(ss, random_traits);
508  } else {
509  ss << ": ";
510  print_trait_list(ss, must_have_traits);
511  }
512  ss << "\n\n";
513  } else {
514  if (line2) {
515  ss << _("Traits") << " (" << (type_.num_traits() - must_have_nameless_traits) << ") : ";
516  print_trait_list(ss, random_traits);
517  ss << "\n\n";
518  }
519  }
520  }
521 
522  // Print the abilities the units has, cross-reference them
523  // to their respective topics. TODO: Update this according to musthave trait effects, similar to movetype below
524  if(!type_.abilities_metadata().empty()) {
525  ss << _("Abilities: ");
526 
527  bool start = true;
528 
529  for(auto iter = type_.abilities_metadata().begin(); iter != type_.abilities_metadata().end(); ++iter) {
530  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
531 
532  if(iter->name.empty()) {
533  continue;
534  }
535 
536  if(!start) {
537  ss << ", ";
538  } else {
539  start = false;
540  }
541 
542  std::string lang_ability = translation::gettext(iter->name.c_str());
543  ss << make_link(lang_ability, ref_id);
544  }
545 
546  ss << "\n\n";
547  }
548 
549  // Print the extra AMLA upgrade abilities, cross-reference them to their respective topics.
550  if(!type_.adv_abilities_metadata().empty()) {
551  ss << _("Ability Upgrades: ");
552 
553  bool start = true;
554 
555  for(auto iter = type_.adv_abilities_metadata().begin(); iter != type_.adv_abilities_metadata().end(); ++iter) {
556  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
557 
558  if(iter->name.empty()) {
559  continue;
560  }
561 
562  if(!start) {
563  ss << ", ";
564  } else {
565  start = false;
566  }
567 
568  std::string lang_ability = translation::gettext(iter->name.c_str());
569  ss << make_link(lang_ability, ref_id);
570  }
571 
572  ss << "\n\n";
573  }
574 
575  // Print some basic information such as HP and movement points.
576  // TODO: Make this info update according to musthave traits, similar to movetype below.
577 
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 << _("HP:") << font::nbsp << type_.hitpoints() << jump(30)
582  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
583  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
584  // unpleasant line breaks (issue #3256).
585  << _("Moves:") << font::nbsp << type_.movement() << jump(30);
586  if (type_.vision() != type_.movement()) {
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 << _("Vision:") << font::nbsp << type_.vision() << jump(30);
591  }
592  if (type_.jamming() > 0) {
593  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
594  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
595  // unpleasant line breaks (issue #3256).
596  ss << _("Jamming:") << font::nbsp << type_.jamming() << jump(30);
597  }
598  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
599  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
600  // unpleasant line breaks (issue #3256).
601  ss << _("Cost:") << font::nbsp << type_.cost() << jump(30)
602  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
603  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
604  // unpleasant line breaks (issue #3256).
605  << _("Alignment:") << font::nbsp
606  << make_link(type_.alignment_description(type_.alignment(), type_.genders().front()), "time_of_day")
607  << jump(30);
609  // TRANSLATORS: This string is used in the help page of a single unit. It uses
610  // non-breaking spaces to prevent unpleasant line breaks (issue #3256). In the
611  // translation use non-breaking spaces as appropriate for the target language.
612  ss << _("Required\u00a0XP:") << font::nbsp << type_.experience_needed();
613  }
614 
615  // Print the detailed description about the unit.
616  ss << "\n\n" << detailed_description;
617  if(const auto notes = type_.special_notes(); !notes.empty()) {
618  ss << "\n\n" << _("Special Notes:") << '\n';
619  for(const auto& note : notes) {
620  ss << font::unicode_bullet << " " << note << '\n';
621  }
622  }
623 
624  // Padding for range and damage type icons
625  const auto padding = 4; // matches the alignment of the terrain rows
626 
627  // Print the different attacks a unit has, if it has any.
628  if (!type_.attacks().empty()) {
629  // Print headers for the table.
630  ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
631  << "'</header>\n\n";
632  table_spec table;
633 
634  std::vector<item> first_row;
635  // Dummy element, icons are below.
636  first_row.push_back(item("", 0));
637  push_header(first_row, _("unit help^Name"));
638  push_header(first_row, _("Strikes"));
639  push_header(first_row, _("Range"));
640  push_header(first_row, _("Type"));
641  push_header(first_row, _("Special"));
642  table.push_back(first_row);
643  // Print information about every attack.
644  for(const attack_type& attack : type_.attacks()) {
645  std::string lang_weapon = attack.name();
646  std::string lang_type = string_table["type_" + attack.type()];
647  std::vector<item> row;
648  std::stringstream attack_ss;
649 
650  // Attack icon
651  attack_ss << "<img>src='" << attack.icon() << "'</img>";
652  row.emplace_back(attack_ss.str(),image_width(attack.icon()));
653  attack_ss.str(clear_stringstream);
654 
655  // Attack name
656  push_tab_pair(row, lang_weapon);
657 
658  // damage x strikes
659  attack_ss << attack.damage() << font::weapon_numbers_sep << attack.num_attacks()
660  << " " << attack.accuracy_parry_description();
661  push_tab_pair(row, attack_ss.str());
662  attack_ss.str(clear_stringstream);
663 
664  // Range, with icon
665  const std::string range_icon = "icons/profiles/" + attack.range() + "_attack.png~SCALE_INTO(16,16)";
666  if (attack.min_range() > 1 || attack.max_range() > 1) {
667  attack_ss << attack.min_range() << "-" << attack.max_range() << ' ';
668  }
669  attack_ss << string_table["range_" + attack.range()];
670  push_tab_pair(row, attack_ss.str(), range_icon, padding);
671  attack_ss.str(clear_stringstream);
672 
673  // Damage type, with icon
674  const std::string type_icon = "icons/profiles/" + attack.type() + ".png~SCALE_INTO(16,16)";
675  push_tab_pair(row, lang_type, type_icon, padding);
676 
677  // Show this attack's special, if it has any. Cross
678  // reference it to the section describing the special.
679  std::vector<std::pair<t_string, t_string>> specials = attack.special_tooltips();
680  if (!specials.empty()) {
681  std::string lang_special = "";
682  const std::size_t specials_size = specials.size();
683  for (std::size_t i = 0; i != specials_size; ++i) {
684  const std::string ref_id = std::string("weaponspecial_")
685  + specials[i].first.base_str();
686  lang_special = (specials[i].first);
687  attack_ss << make_link(lang_special, ref_id);
688  if (i+1 != specials_size) {
689  attack_ss << ", "; //comma placed before next special
690  }
691  }
692  row.emplace_back(attack_ss.str(), font::pango_line_width(lang_special, normal_font_size));
693  }
694  table.push_back(row);
695  }
696  ss << generate_table(table);
697  }
698 
699  // Generate the movement type of the unit, with resistance, defense, movement, jamming and vision data updated according to any 'musthave' traits which always apply
700  movetype movement_type = type_.movement_type();
702  if (!traits.empty() && type_.num_traits() > 0) {
703  for (const config & t : traits) {
704  if (t["availability"].str() == "musthave") {
705  for (const config & effect : t.child_range("effect")) {
706  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.
707  && movetype::effects.find(effect["apply_to"].str()) != movetype::effects.end()) {
708  movement_type.merge(effect, effect["replace"].to_bool());
709  }
710  }
711  }
712  }
713  }
714 
715  // Print the resistance table of the unit.
716  ss << "\n\n<header>text='" << escape(_("Resistances"))
717  << "'</header>\n\n";
718  table_spec resistance_table;
719  std::vector<item> first_res_row;
720  push_header(first_res_row, _("Attack Type"));
721  push_header(first_res_row, _("Resistance"));
722  resistance_table.push_back(first_res_row);
723  utils::string_map_res dam_tab = movement_type.damage_table();
724  for(std::pair<std::string, std::string> dam_it : dam_tab) {
725  std::vector<item> row;
726  int resistance = 100;
727  try {
728  resistance -= std::stoi(dam_it.second);
729  } catch(std::invalid_argument&) {}
730  std::string resist = std::to_string(resistance) + '%';
731  const std::size_t pos = resist.find('-');
732  if (pos != std::string::npos) {
733  resist.replace(pos, 1, font::unicode_minus);
734  }
735  std::string color = unit_helper::resistance_color(resistance);
736  const std::string lang_type = string_table["type_" + dam_it.first];
737  const std::string type_icon = "icons/profiles/" + dam_it.first + ".png~SCALE_INTO(16,16)";
738  push_tab_pair(row, lang_type, type_icon, padding);
739  std::stringstream str;
740  str << "<format>color=\"" << color << "\" text='"<< resist << "'</format>";
741  const std::string markup = str.str();
742  str.str(clear_stringstream);
743  str << resist;
744  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
745  resistance_table.push_back(row);
746  }
747  ss << generate_table(resistance_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\n<header>text='" << escape(_("Terrain Modifiers"))
752  << "'</header>\n\n";
753  std::vector<item> first_row;
754  table_spec table;
755  push_header(first_row, _("Terrain"));
756  push_header(first_row, _("Defense"));
757  push_header(first_row, _("Movement Cost"));
758 
759  const bool has_terrain_defense_caps = movement_type.has_terrain_defense_caps(prefs::get().encountered_terrains());
760  if (has_terrain_defense_caps) {
761  push_header(first_row, _("Defense Cap"));
762  }
763 
764  const bool has_vision = type_.movement_type().has_vision_data();
765  if (has_vision) {
766  push_header(first_row, _("Vision Cost"));
767  }
768  const bool has_jamming = type_.movement_type().has_jamming_data();
769  if (has_jamming) {
770  push_header(first_row, _("Jamming Cost"));
771  }
772 
773  table.push_back(first_row);
774 
775  std::set<terrain_movement_info> terrain_moves;
776 
777  for (t_translation::terrain_code terrain : prefs::get().encountered_terrains()) {
779  continue;
780  }
781  const terrain_type& info = tdata->get_terrain_info(terrain);
782  const int moves = movement_type.movement_cost(terrain);
783  const bool cannot_move = moves > type_.movement();
784  if (cannot_move && info.hide_if_impassable()) {
785  continue;
786  }
787 
788  if (info.is_indivisible() && info.is_nonnull()) {
789  terrain_movement_info movement_info =
790  {
791  info.name(),
792  info.id(),
793  100 - movement_type.defense_modifier(terrain),
794  moves,
795  movement_type.vision_cost(terrain),
796  movement_type.jamming_cost(terrain),
797  movement_type.get_defense().capped(terrain)
798  };
799 
800  terrain_moves.insert(movement_info);
801  }
802  }
803 
804  for(const terrain_movement_info &m : terrain_moves)
805  {
806  std::vector<item> row;
807 
808  bool high_res = false;
809  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
810  const std::string terrain_image = "icons/terrain/terrain_type_" + m.id + (high_res ? "_30.png" : ".png");
811 
812  const std::string final_image = tc_base + "~RC(magenta>" + m.id + ")~BLIT(" + terrain_image + ")";
813 
814  row.emplace_back("<img>src='" + final_image + "'</img> " +
815  make_link(m.name, "..terrain_" + m.id),
816  font::pango_line_width(m.name, normal_font_size) + (high_res ? 32 : 16) );
817 
818  //defense - range: +10 % .. +70 %
819  // passing false to select the more saturated red-to-green scale
820  std::string color = game_config::red_to_green(m.defense, false).to_hex_string();
821 
822  std::stringstream str;
823  std::stringstream str_unformatted;
824  str << "<format>color='" << color << "' text='"<< m.defense << "%'</format>";
825  str_unformatted << m.defense << "%";
826  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
827 
828  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
829  str.str(clear_stringstream);
830  str_unformatted.str(clear_stringstream);
831  const bool cannot_move = m.movement_cost > type_.movement(); // cannot move in this terrain
832  double movement_red_to_green = 100.0 - 25.0 * m.movement_cost;
833 
834  // passing true to select the less saturated red-to-green scale
835  std::string movement_color = game_config::red_to_green(movement_red_to_green, true).to_hex_string();
836  str << "<format>color='" << movement_color << "' text='";
837  // A 5 MP margin; if the movement costs go above
838  // the unit's max moves + 5, we replace it with dashes.
839  if(cannot_move && (m.movement_cost > type_.movement() + 5)) {
840  str_unformatted << font::unicode_figure_dash;
841  } else if(cannot_move) {
842  str_unformatted << "(" << m.movement_cost << ")";
843  } else {
844  str_unformatted << m.movement_cost;
845  }
846  if(m.movement_cost != 0) {
847  const int movement_hexes_per_turn = type_.movement() / m.movement_cost;
848  str_unformatted << " ";
849  for(int i = 0; i < movement_hexes_per_turn; ++i) {
850  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
851  str_unformatted << "\u2b23\u200b";
852  }
853  }
854  str << str_unformatted.str() << "'</format>";
855  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
856 
857  //defense cap
858  if (has_terrain_defense_caps) {
859  str.str(clear_stringstream);
860  str_unformatted.str(clear_stringstream);
861  if (m.defense_cap) {
862  str << "<format>color='"<< color <<"' text='" << m.defense << "%'</format>";
863  str_unformatted << m.defense << "%";
864  } else {
865  str << "<format>color=white text='" << font::unicode_figure_dash << "'</format>";
866  str_unformatted << font::unicode_figure_dash;
867  }
868  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
869  }
870 
871  //vision
872  if (has_vision) {
873  str.str(clear_stringstream);
874  str_unformatted.str(clear_stringstream);
875  const bool cannot_view = m.vision_cost > type_.vision(); // cannot view in this terrain
876  double vision_red_to_green = 100.0 - 25.0 * m.vision_cost;
877 
878  // passing true to select the less saturated red-to-green scale
879  std::string vision_color = game_config::red_to_green(vision_red_to_green, true).to_hex_string();
880  str << "<format>color='" << vision_color << "' text='";
881  // A 5 MP margin; if the vision costs go above
882  // the unit's vision + 5, we replace it with dashes.
883  if(cannot_view && (m.vision_cost > type_.vision() + 5)) {
884  str_unformatted << font::unicode_figure_dash;
885  } else if(cannot_view) {
886  str_unformatted << "(" << m.vision_cost << ")";
887  } else {
888  str_unformatted << m.vision_cost;
889  }
890  if(m.vision_cost != 0) {
891  const int vision_hexes_per_turn = type_.vision() / m.vision_cost;
892  str_unformatted << " ";
893  for(int i = 0; i < vision_hexes_per_turn; ++i) {
894  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
895  str_unformatted << "\u2b23\u200b";
896  }
897  }
898  str << str_unformatted.str() << "'</format>";
899  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
900  }
901 
902  //jamming
903  if (has_jamming) {
904  str.str(clear_stringstream);
905  str_unformatted.str(clear_stringstream);
906  const bool cannot_jam = m.jamming_cost > type_.jamming(); // cannot jam in this terrain
907  double jamming_red_to_green = 100.0 - 25.0 * m.jamming_cost;
908 
909  // passing true to select the less saturated red-to-green scale
910  std::string jamming_color = game_config::red_to_green(jamming_red_to_green, true).to_hex_string();
911  str << "<format>color='" << jamming_color << "' text='";
912  // A 5 MP margin; if the jamming costs go above
913  // the unit's jamming + 5, we replace it with dashes.
914  if (cannot_jam && m.jamming_cost > type_.jamming() + 5) {
915  str_unformatted << font::unicode_figure_dash;
916  } else if(cannot_jam) {
917  str_unformatted << "(" << m.jamming_cost << ")";
918  } else {
919  str_unformatted << m.jamming_cost;
920  }
921  if(m.jamming_cost != 0) {
922  const int jamming_hexes_per_turn = type_.jamming() / m.jamming_cost;
923  str_unformatted << " ";
924  for(int i = 0; i < jamming_hexes_per_turn; ++i) {
925  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
926  str_unformatted << "\u2b23\u200b";
927  }
928  }
929  str << str_unformatted.str() << "'</format>";
930  row.emplace_back(str.str(), font::pango_line_width(str_unformatted.str(), normal_font_size));
931  }
932 
933  table.push_back(row);
934  }
935 
936  ss << generate_table(table);
937  } else {
938  WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.";
939  }
940  return ss.str();
941 }
942 
943 void unit_topic_generator::push_header(std::vector< item > &row, const std::string& name) const {
945 }
946 
947 } // 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: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: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: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 VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#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:243
std::string bold(const std::string &s)
Definition: help_impl.hpp:402
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:382
std::string escape(const std::string &s)
Prepend all chars with meaning inside attributes with a backslash.
Definition: help_impl.cpp:1727
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:89
unsigned image_width(const std::string &filename)
Definition: help_impl.cpp:1660
const std::string variation_prefix
Definition: help_impl.cpp:94
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:95
std::vector< std::vector< help::item > > table_spec
Definition: help_impl.hpp:412
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:1670
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
std::pair< std::string, std::string > trait_data
const int normal_font_size
Definition: help_impl.cpp:81
std::string generate_table(const table_spec &tab, const unsigned int spacing)
Definition: help_impl.cpp:1684
const std::string terrain_prefix
Definition: help_impl.cpp:90
const std::string unknown_unit_topic
Definition: help_impl.cpp:88
std::shared_ptr< terrain_type_data > load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1733
static std::string best_str(bool best)
std::string jump(const unsigned amount)
Definition: help_impl.hpp:395
static void print_trait_list(std::stringstream &ss, const std::vector< trait_data > &l)
logger & info()
Definition: log.cpp:314
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: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