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