The Battle for Wesnoth  1.17.0-dev
help_topic_generators.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
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 "game_config.hpp" // for debug, menu_contract, etc
22 #include "preferences/game.hpp" // for encountered_terrains, 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 "units/race.hpp" // for unit_race, etc
28 #include "terrain/terrain.hpp" // for terrain_type
29 #include "terrain/translation.hpp" // for operator==, ter_list, etc
30 #include "terrain/type_data.hpp" // for terrain_type_data, etc
31 #include "tstring.hpp" // for t_string, operator<<
32 #include "units/helper.hpp" // for resistance_color
33 #include "units/types.hpp" // for unit_type, unit_type_data, etc
34 #include <optional>
35 #include "video.hpp" // fore current_resolution
36 
37 #include <iostream> // for operator<<, basic_ostream, etc
38 #include <map> // for map, etc
39 #include <set>
40 
41 static lg::log_domain log_help("help");
42 #define WRN_HP LOG_STREAM(warn, log_help)
43 #define DBG_HP LOG_STREAM(debug, log_help)
44 
45 namespace help {
46 
48 {
49  const t_string name;
50  const t_string id;
51  const int defense;
52  const int movement_cost;
53  const int vision_cost;
54  const int jamming_cost;
55  const bool defense_cap;
56 
57  bool operator<(const terrain_movement_info& other) const
58  {
59  return translation::icompare(name, other.name) < 0;
60  }
61 };
62 
63 static std::string best_str(bool best) {
64  std::string lang_policy = (best ? _("Best of") : _("Worst of"));
65  std::string color_policy = (best ? "green": "red");
66 
67  return "<format>color='" + color_policy + "' text='" + lang_policy + "'</format>";
68 }
69 
70 typedef t_translation::ter_list::const_iterator ter_iter;
71 // Gets an english description of a terrain ter_list alias behavior: "Best of cave, hills", "Worst of Swamp, Forest" etc.
72 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)
73 {
74 
75  if (start == end) return "";
76  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.
77 
78  std::optional<ter_iter> last_change_pos;
79 
80  bool best = begin_best;
81  for (ter_iter i = start; i != end; ++i) {
82  if ((best && *i == t_translation::MINUS) || (!best && *i == t_translation::PLUS)) {
83  best = !best;
84  last_change_pos = i;
85  }
86  }
87 
88  std::stringstream ss;
89 
90  if (!last_change_pos) {
91  std::vector<std::string> names;
92  for (ter_iter i = start; i != end; ++i) {
93  const terrain_type tt = tdata->get_terrain_info(*i);
94  if (!tt.editor_name().empty())
95  names.push_back(tt.editor_name());
96  }
97 
98  if (names.empty()) return "";
99  if (names.size() == 1) return names.at(0);
100 
101  ss << best_str(best) << " ";
102  if (!first_level) ss << "( ";
103  ss << names.at(0);
104 
105  for (std::size_t i = 1; i < names.size(); i++) {
106  ss << ", " << names.at(i);
107  }
108 
109  if (!first_level) ss << " )";
110  } else {
111  std::vector<std::string> names;
112  for (ter_iter i = *last_change_pos+1; i != end; ++i) {
113  const terrain_type tt = tdata->get_terrain_info(*i);
114  if (!tt.editor_name().empty())
115  names.push_back(tt.editor_name());
116  }
117 
118  if (names.empty()) { //This alias list is apparently padded with junk at the end, so truncate it without adding more parens
119  return print_behavior_description(start, *last_change_pos, tdata, first_level, begin_best);
120  }
121 
122  ss << best_str(best) << " ";
123  if (!first_level) ss << "( ";
124  ss << print_behavior_description(start, *last_change_pos-1, tdata, false, begin_best);
125  // Printed the (parenthesized) leading part from before the change, now print the remaining names in this group.
126  for (const std::string & s : names) {
127  ss << ", " << s;
128  }
129  if (!first_level) ss << " )";
130  }
131  return ss.str();
132 }
133 
135  std::stringstream ss;
136 
137  if (!type_.icon_image().empty())
138  ss << "<img>src='images/buttons/icon-base-32.png~RC(magenta>" << type_.id()
139  << ")~BLIT("<< "terrain/" << type_.icon_image() << "_30.png)" << "'</img> ";
140 
141  if (!type_.editor_image().empty())
142  ss << "<img>src='" << type_.editor_image() << "'</img> ";
143 
144  if (!type_.help_topic_text().empty())
145  ss << "\n\n" << type_.help_topic_text().str() << "\n";
146  else
147  ss << "\n";
148 
149  std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data();
150 
151  if (!tdata) {
152  WRN_HP << "When building terrain help topics, we couldn't acquire any terrain types data\n";
153  return ss.str();
154  }
155 
156  if (!type_.is_indivisible()) {
157  ss << "\n" << _("Base Terrain: ");
158 
159  bool first = true;
160  for (const auto& underlying_terrain : type_.union_type()) {
161  const terrain_type& base = tdata->get_terrain_info(underlying_terrain);
162 
163  if (base.editor_name().empty()) continue;
164 
165  if (!first) {
166  ss << ", ";
167  } else {
168  first = false;
169  }
170 
171  ss << make_link(base.editor_name(), ".." + terrain_prefix + base.id());
172  }
173 
174  ss << "\n";
175 
176  const t_translation::ter_list& underlying_mvt_terrains = type_.mvt_type();
177  ss << "\n" << _("Movement properties: ");
178  ss << print_behavior_description(underlying_mvt_terrains.begin(), underlying_mvt_terrains.end(), tdata) << "\n";
179 
180  const t_translation::ter_list& underlying_def_terrains = type_.def_type();
181  ss << "\n" << _("Defense properties: ");
182  ss << print_behavior_description(underlying_def_terrains.begin(), underlying_def_terrains.end(), tdata) << "\n";
183  }
184 
185  if (game_config::debug) {
186 
187  ss << "\n";
188  ss << "ID: " << type_.id() << "\n";
189 
190  ss << "Village: " << (type_.is_village() ? "Yes" : "No") << "\n";
191  ss << "Gives Healing: " << type_.gives_healing() << "\n";
192 
193  ss << "Keep: " << (type_.is_keep() ? "Yes" : "No") << "\n";
194  ss << "Castle: " << (type_.is_castle() ? "Yes" : "No") << "\n";
195 
196  ss << "Overlay: " << (type_.is_overlay() ? "Yes" : "No") << "\n";
197  ss << "Combined: " << (type_.is_combined() ? "Yes" : "No") << "\n";
198  ss << "Nonnull: " << (type_.is_nonnull() ? "Yes" : "No") << "\n";
199 
200  ss << "Terrain string:" << type_.number() << "\n";
201 
202  ss << "Hide in Editor: " << (type_.hide_in_editor() ? "Yes" : "No") << "\n";
203  ss << "Editor Group: " << type_.editor_group() << "\n";
204 
205  ss << "Light Bonus: " << type_.light_bonus(0) << "\n";
206 
207  ss << type_.income_description();
208 
209  if (type_.editor_image().empty()) { // Note: this is purely temporary to help make a different help entry
210  ss << "\nEditor Image: Empty\n";
211  } else {
212  ss << "\nEditor Image: " << type_.editor_image() << "\n";
213  }
214 
215  const t_translation::ter_list& underlying_mvt_terrains = tdata->underlying_mvt_terrain(type_.number());
216  ss << "\nDebug Mvt Description String:";
217  for (const t_translation::terrain_code & t : underlying_mvt_terrains) {
218  ss << " " << t;
219  }
220 
221  const t_translation::ter_list& underlying_def_terrains = tdata->underlying_def_terrain(type_.number());
222  ss << "\nDebug Def Description String:";
223  for (const t_translation::terrain_code & t : underlying_def_terrains) {
224  ss << " " << t;
225  }
226 
227  }
228 
229  return ss.str();
230 }
231 
232 
233 //Typedef to help with formatting list of traits
234 // Maps localized trait name to trait help topic ID
235 typedef std::pair<std::string, std::string> trait_data;
236 
237 //Helper function for printing a list of trait data
238 static void print_trait_list(std::stringstream & ss, const std::vector<trait_data> & l)
239 {
240  std::size_t i = 0;
241  ss << make_link(l[i].first, l[i].second);
242 
243  // This doesn't skip traits with empty names
244  for(i++; i < l.size(); i++) {
245  ss << ", " << make_link(l[i].first,l[i].second);
246  }
247 }
248 
249 std::string unit_topic_generator::operator()() const {
250  // Force the lazy loading to build this unit.
252 
253  std::stringstream ss;
254  std::string clear_stringstream;
255  const std::string detailed_description = type_.unit_description();
256  const unit_type& female_type = type_.get_gender_unit_type(unit_race::FEMALE);
257  const unit_type& male_type = type_.get_gender_unit_type(unit_race::MALE);
258 
260 
261  ss << _("Level") << " " << type_.level();
262  ss << "\n\n";
263 
264  ss << "<img>src='" << male_type.image();
265  ss << "~RC(" << male_type.flag_rgb() << ">red)";
266  if (screen_width >= 1200) ss << "~XBRZ(2)";
267  ss << "' box='no'</img> ";
268 
269 
270  if (&female_type != &male_type) {
271  ss << "<img>src='" << female_type.image();
272  ss << "~RC(" << female_type.flag_rgb() << ">red)";
273  if (screen_width >= 1200) ss << "~XBRZ(2)";
274  ss << "' box='no'</img> ";
275  }
276 
277  const std::string &male_portrait = male_type.small_profile().empty() ?
278  male_type.big_profile() : male_type.small_profile();
279  const std::string &female_portrait = female_type.small_profile().empty() ?
280  female_type.big_profile() : female_type.small_profile();
281 
282  const bool has_male_portrait = !male_portrait.empty() && male_portrait != male_type.image() && male_portrait != "unit_image";
283  const bool has_female_portrait = !female_portrait.empty() && female_portrait != male_portrait && female_portrait != female_type.image() && female_portrait != "unit_image";
284 
285  int sz = (has_male_portrait && has_female_portrait ? 300 : 400);
286  if (screen_width <= 1366) {
287  sz = (has_male_portrait && has_female_portrait ? 200 : 300);
288  } else if (screen_width >= 1920) {
289  sz = 400;
290  }
291 
292  // TODO: figure out why the second checks don't match but the last does
293  if (has_male_portrait) {
294  ss << "<img>src='" << male_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
295  }
296 
297 
298  if (has_female_portrait) {
299  ss << "<img>src='" << female_portrait << "~FL(horiz)~SCALE_INTO(" << sz << ',' << sz << ")' box='no' align='right' float='yes'</img> ";
300  }
301 
302  ss << "\n\n\n";
303 
304  // Print cross-references to units that this unit advances from/to.
305  // Cross reference to the topics containing information about those units.
306  const bool first_reverse_value = true;
307  bool reverse = first_reverse_value;
308  if (variation_.empty()) {
309  do {
310  std::vector<std::string> adv_units =
311  reverse ? type_.advances_from() : type_.advances_to();
312  bool first = true;
313 
314  for (const std::string &adv : adv_units) {
316  if (!type || type->hide_help()) {
317  continue;
318  }
319 
320  if (first) {
321  if (reverse) {
322  ss << _("Advances from: ");
323  } else {
324  ss << _("Advances to: ");
325  }
326  first = false;
327  } else {
328  ss << ", ";
329  }
330 
331  std::string lang_unit = type->type_name();
332  std::string ref_id;
333  if (description_type(*type) == FULL_DESCRIPTION) {
334  const std::string section_prefix = type->show_variations_in_help() ? ".." : "";
335  ref_id = section_prefix + unit_prefix + type->id();
336  } else {
337  ref_id = unknown_unit_topic;
338  lang_unit += " (?)";
339  }
340  ss << make_link(lang_unit, ref_id);
341  }
342  if (!first) {
343  ss << "\n";
344  }
345 
346  reverse = !reverse; //switch direction
347  } while(reverse != first_reverse_value); // don't restart
348  }
349 
350  const unit_type* parent = variation_.empty() ? &type_ :
352  if (!variation_.empty()) {
353  ss << _("Base unit: ") << make_link(parent->type_name(), ".." + unit_prefix + type_.id()) << "\n";
354  } else {
355  bool first = true;
356  for (const std::string& base_id : utils::split(type_.get_cfg()["base_ids"])) {
357  if (first) {
358  ss << _("Base units: ");
359  first = false;
360  }
361  const unit_type* base_type = unit_types.find(base_id, unit_type::HELP_INDEXED);
362  const std::string section_prefix = base_type->show_variations_in_help() ? ".." : "";
363  ss << make_link(base_type->type_name(), section_prefix + unit_prefix + base_id) << "\n";
364  }
365  }
366 
367  bool first = true;
368  for (const std::string &var_id : parent->variations()) {
369  const unit_type &type = parent->get_variation(var_id);
370 
371  if(type.hide_help()) {
372  continue;
373  }
374 
375  if (first) {
376  ss << _("Variations: ");
377  first = false;
378  } else {
379  ss << ", ";
380  }
381 
382  std::string ref_id;
383 
384  std::string var_name = type.variation_name();
385  if (description_type(type) == FULL_DESCRIPTION) {
386  ref_id = variation_prefix + type.id() + "_" + var_id;
387  } else {
388  ref_id = unknown_unit_topic;
389  var_name += " (?)";
390  }
391 
392  ss << make_link(var_name, ref_id);
393  }
394  ss << "\n"; //added even if empty, to avoid shifting
395 
396  // Print the race of the unit, cross-reference it to the respective topic.
397  const std::string race_id = type_.race_id();
398  std::string race_name = type_.race()->plural_name();
399  if (race_name.empty()) {
400  race_name = _ ("race^Miscellaneous");
401  }
402  ss << _("Race: ");
403  ss << make_link(race_name, "..race_" + race_id);
404  ss << "\n\n";
405 
406  // Print the possible traits of the unit, cross-reference them
407  // to their respective topics.
408  if (config::const_child_itors traits = type_.possible_traits()) {
409  std::vector<trait_data> must_have_traits;
410  std::vector<trait_data> random_traits;
411  int must_have_nameless_traits = 0;
412 
413  for(const config& trait : traits) {
414  const std::string& male_name = trait["male_name"].str();
415  const std::string& female_name = trait["female_name"].str();
416  std::string trait_name;
417  if (type_.has_gender_variation(unit_race::MALE) && ! male_name.empty())
418  trait_name = male_name;
419  else if (type_.has_gender_variation(unit_race::FEMALE) && ! female_name.empty())
420  trait_name = female_name;
421  else if (! trait["name"].str().empty())
422  trait_name = trait["name"].str();
423  else
424  continue; // Hidden trait
425 
426  std::string lang_trait_name = translation::gettext(trait_name.c_str());
427  if (lang_trait_name.empty() && trait["availability"].str() == "musthave") {
428  ++must_have_nameless_traits;
429  continue;
430  }
431  const std::string ref_id = "traits_"+trait["id"].str();
432  ((trait["availability"].str() == "musthave") ? must_have_traits : random_traits).emplace_back(lang_trait_name, ref_id);
433  }
434 
435  bool line1 = !must_have_traits.empty();
436  bool line2 = !random_traits.empty() && type_.num_traits() > must_have_traits.size();
437 
438  if (line1) {
439  std::string traits_label = _("Traits");
440  ss << traits_label;
441  if (line2) {
442  std::stringstream must_have_count;
443  must_have_count << " (" << must_have_traits.size() << ") : ";
444  std::stringstream random_count;
445  random_count << " (" << (type_.num_traits() - must_have_traits.size() - must_have_nameless_traits) << ") : ";
446 
447  int second_line_whitespace = font::pango_line_width(traits_label+must_have_count.str(), normal_font_size)
448  - font::pango_line_width(random_count.str(), normal_font_size);
449  // This ensures that the second line is justified so that the ':' characters are aligned.
450 
451  ss << must_have_count.str();
452  print_trait_list(ss, must_have_traits);
453  ss << "\n" << jump(second_line_whitespace) << random_count.str();
454  print_trait_list(ss, random_traits);
455  } else {
456  ss << ": ";
457  print_trait_list(ss, must_have_traits);
458  }
459  ss << "\n\n";
460  } else {
461  if (line2) {
462  ss << _("Traits") << " (" << (type_.num_traits() - must_have_nameless_traits) << ") : ";
463  print_trait_list(ss, random_traits);
464  ss << "\n\n";
465  }
466  }
467  }
468 
469  // Print the abilities the units has, cross-reference them
470  // to their respective topics. TODO: Update this according to musthave trait effects, similar to movetype below
471  if(!type_.abilities_metadata().empty()) {
472  ss << _("Abilities: ");
473 
474  bool start = true;
475 
476  for(auto iter = type_.abilities_metadata().begin(); iter != type_.abilities_metadata().end(); ++iter) {
477  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
478 
479  if(iter->name.empty()) {
480  continue;
481  }
482 
483  if(!start) {
484  ss << ", ";
485  } else {
486  start = false;
487  }
488 
489  std::string lang_ability = translation::gettext(iter->name.c_str());
490  ss << make_link(lang_ability, ref_id);
491  }
492 
493  ss << "\n\n";
494  }
495 
496  // Print the extra AMLA upgrade abilities, cross-reference them to their respective topics.
497  if(!type_.adv_abilities_metadata().empty()) {
498  ss << _("Ability Upgrades: ");
499 
500  bool start = true;
501 
502  for(auto iter = type_.adv_abilities_metadata().begin(); iter != type_.adv_abilities_metadata().end(); ++iter) {
503  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
504 
505  if(iter->name.empty()) {
506  continue;
507  }
508 
509  if(!start) {
510  ss << ", ";
511  } else {
512  start = false;
513  }
514 
515  std::string lang_ability = translation::gettext(iter->name.c_str());
516  ss << make_link(lang_ability, ref_id);
517  }
518 
519  ss << "\n\n";
520  }
521 
522  // Print some basic information such as HP and movement points.
523  // TODO: Make this info update according to musthave traits, similar to movetype below.
524 
525  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
526  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
527  // unpleasant line breaks (issue #3256).
528  ss << _("HP:") << font::nbsp << type_.hitpoints() << jump(30)
529  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
530  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
531  // unpleasant line breaks (issue #3256).
532  << _("Moves:") << font::nbsp << type_.movement() << jump(30);
533  if (type_.vision() != type_.movement()) {
534  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
535  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
536  // unpleasant line breaks (issue #3256).
537  ss << _("Vision:") << font::nbsp << type_.vision() << jump(30);
538  }
539  if (type_.jamming() > 0) {
540  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
541  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
542  // unpleasant line breaks (issue #3256).
543  ss << _("Jamming:") << font::nbsp << type_.jamming() << jump(30);
544  }
545  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
546  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
547  // unpleasant line breaks (issue #3256).
548  ss << _("Cost:") << font::nbsp << type_.cost() << jump(30)
549  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
550  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
551  // unpleasant line breaks (issue #3256).
552  << _("Alignment:") << font::nbsp
553  << make_link(type_.alignment_description(type_.alignment(), type_.genders().front()), "time_of_day")
554  << jump(30);
555  if (type_.can_advance() || type_.modification_advancements()) {
556  // TRANSLATORS: This string is used in the help page of a single unit. It uses
557  // non-breaking spaces to prevent unpleasant line breaks (issue #3256). In the
558  // translation use non-breaking spaces as appropriate for the target language.
559  ss << _("Required\u00a0XP:") << font::nbsp << type_.experience_needed();
560  }
561 
562  // Print the detailed description about the unit.
563  ss << "\n\n" << detailed_description;
564  if(const auto notes = type_.special_notes(); !notes.empty()) {
565  ss << "\n\n" << _("Special Notes:") << '\n';
566  for(const auto& note : notes) {
567  ss << "• " << note << '\n';
568  }
569  }
570 
571  // Print the different attacks a unit has, if it has any.
572  if (!type_.attacks().empty()) {
573  // Print headers for the table.
574  ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
575  << "'</header>\n\n";
576  table_spec table;
577 
578  std::vector<item> first_row;
579  // Dummy element, icons are below.
580  first_row.push_back(item("", 0));
581  push_header(first_row, _("unit help^Name"));
582  push_header(first_row, _("Strikes"));
583  push_header(first_row, _("Range"));
584  push_header(first_row, _("Type"));
585  push_header(first_row, _("Special"));
586  table.push_back(first_row);
587  // Print information about every attack.
588  for(const attack_type& attack : type_.attacks()) {
589  std::string lang_weapon = attack.name();
590  std::string lang_type = string_table["type_" + attack.type()];
591  std::vector<item> row;
592  std::stringstream attack_ss;
593 
594  // Attack icon
595  attack_ss << "<img>src='" << attack.icon() << "'</img>";
596  row.emplace_back(attack_ss.str(),image_width(attack.icon()));
597  attack_ss.str(clear_stringstream);
598 
599  // Attack name
600  push_tab_pair(row, lang_weapon);
601 
602  // damage x strikes
603  attack_ss << attack.damage() << font::weapon_numbers_sep << attack.num_attacks()
604  << " " << attack.accuracy_parry_description();
605  push_tab_pair(row, attack_ss.str());
606  attack_ss.str(clear_stringstream);
607 
608  // Padding for range and damage type icons
609  const auto padding = 5; // TODO amount of padding?
610 
611  // Range, with icon
612  const std::string range_icon = "icons/profiles/" + attack.range() + "_attack.png";
613  if (attack.min_range() > 1 || attack.max_range() > 1) {
614  attack_ss << attack.min_range() << "-" << attack.max_range() << ' ';
615  }
616  attack_ss << string_table["range_" + attack.range()];
617  push_tab_pair(row, attack_ss.str(), range_icon, padding);
618  attack_ss.str(clear_stringstream);
619 
620  // Damage type, with icon
621  const std::string type_icon = "icons/profiles/" + attack.type() + ".png";
622  push_tab_pair(row, lang_type, type_icon, padding);
623 
624  // Show this attack's special, if it has any. Cross
625  // reference it to the section describing the special.
626  std::vector<std::pair<t_string, t_string>> specials = attack.special_tooltips();
627  if (!specials.empty()) {
628  std::string lang_special = "";
629  const std::size_t specials_size = specials.size();
630  for (std::size_t i = 0; i != specials_size; ++i) {
631  const std::string ref_id = std::string("weaponspecial_")
632  + specials[i].first.base_str();
633  lang_special = (specials[i].first);
634  attack_ss << make_link(lang_special, ref_id);
635  if (i+1 != specials_size) {
636  attack_ss << ", "; //comma placed before next special
637  }
638  }
639  row.emplace_back(attack_ss.str(), font::pango_line_width(lang_special, normal_font_size));
640  }
641  table.push_back(row);
642  }
643  ss << generate_table(table);
644  }
645 
646  // Generate the movement type of the unit, with resistance, defense, movement, jamming and vision data updated according to any 'musthave' traits which always apply
647  movetype movement_type = type_.movement_type();
648  config::const_child_itors traits = type_.possible_traits();
649  if (!traits.empty() && type_.num_traits() > 0) {
650  for (const config & t : traits) {
651  if (t["availability"].str() == "musthave") {
652  for (const config & effect : t.child_range("effect")) {
653  if (!effect.child("filter") // If this is musthave but has a unit filter, it might not always apply, so don't apply it in the help.
654  && movetype::effects.find(effect["apply_to"].str()) != movetype::effects.end()) {
655  movement_type.merge(effect, effect["replace"].to_bool());
656  }
657  }
658  }
659  }
660  }
661 
662  // Print the resistance table of the unit.
663  ss << "\n\n<header>text='" << escape(_("Resistances"))
664  << "'</header>\n\n";
665  table_spec resistance_table;
666  std::vector<item> first_res_row;
667  push_header(first_res_row, _("Attack Type"));
668  push_header(first_res_row, _("Resistance"));
669  resistance_table.push_back(first_res_row);
670  utils::string_map dam_tab = movement_type.damage_table();
671  for(std::pair<std::string, std::string> dam_it : dam_tab) {
672  std::vector<item> row;
673  int resistance = 100;
674  try {
675  resistance -= std::stoi(dam_it.second);
676  } catch(std::invalid_argument&) {}
677  std::string resist = std::to_string(resistance) + '%';
678  const std::size_t pos = resist.find('-');
679  if (pos != std::string::npos) {
680  resist.replace(pos, 1, font::unicode_minus);
681  }
682  std::string color = unit_helper::resistance_color(resistance);
683  std::string lang_weapon = string_table["type_" + dam_it.first];
684  push_tab_pair(row, lang_weapon);
685  std::stringstream str;
686  str << "<format>color=\"" << color << "\" text='"<< resist << "'</format>";
687  const std::string markup = str.str();
688  str.str(clear_stringstream);
689  str << resist;
690  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
691  resistance_table.push_back(row);
692  }
693  ss << generate_table(resistance_table);
694 
695  if (std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data()) {
696  // Print the terrain modifier table of the unit.
697  ss << "\n\n<header>text='" << escape(_("Terrain Modifiers"))
698  << "'</header>\n\n";
699  std::vector<item> first_row;
700  table_spec table;
701  push_header(first_row, _("Terrain"));
702  push_header(first_row, _("Defense"));
703  push_header(first_row, _("Movement Cost"));
704 
705  const bool has_terrain_defense_caps = movement_type.has_terrain_defense_caps(preferences::encountered_terrains());
706  if (has_terrain_defense_caps) {
707  push_header(first_row, _("Defense Cap"));
708  }
709 
710  const bool has_vision = type_.movement_type().has_vision_data();
711  if (has_vision) {
712  push_header(first_row, _("Vision Cost"));
713  }
714  const bool has_jamming = type_.movement_type().has_jamming_data();
715  if (has_jamming) {
716  push_header(first_row, _("Jamming Cost"));
717  }
718 
719  table.push_back(first_row);
720 
721  std::set<terrain_movement_info> terrain_moves;
722 
725  continue;
726  }
727  const terrain_type& info = tdata->get_terrain_info(terrain);
728  const int moves = movement_type.movement_cost(terrain);
729  const bool cannot_move = moves > type_.movement();
730  if (cannot_move && info.hide_if_impassable()) {
731  continue;
732  }
733 
734  if (info.is_indivisible() && info.is_nonnull()) {
735  terrain_movement_info movement_info =
736  {
737  info.name(),
738  info.id(),
739  100 - movement_type.defense_modifier(terrain),
740  moves,
741  movement_type.vision_cost(terrain),
742  movement_type.jamming_cost(terrain),
743  movement_type.get_defense().capped(terrain)
744  };
745 
746  terrain_moves.insert(movement_info);
747  }
748  }
749 
750  for(const terrain_movement_info &m : terrain_moves)
751  {
752  std::vector<item> row;
753 
754  bool high_res = false;
755  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
756  const std::string terrain_image = "icons/terrain/terrain_type_" + m.id + (high_res ? "_30.png" : ".png");
757 
758  const std::string final_image = tc_base + "~RC(magenta>" + m.id + ")~BLIT(" + terrain_image + ")";
759 
760  row.emplace_back("<img>src='" + final_image + "'</img> " +
761  make_link(m.name, "..terrain_" + m.id),
762  font::pango_line_width(m.name, normal_font_size) + (high_res ? 32 : 16) );
763 
764  //defense - range: +10 % .. +70 %
765  // passing false to select the more saturated red-to-green scale
766  std::string color = game_config::red_to_green(m.defense, false).to_hex_string();
767 
768  std::stringstream str;
769  str << "<format>color='" << color << "' text='"<< m.defense << "%'</format>";
770  std::string markup = str.str();
771  str.str(clear_stringstream);
772  str << m.defense << "%";
773  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
774 
775  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
776  str.str(clear_stringstream);
777  bool cannot_move = m.movement_cost > type_.movement();
778  if (cannot_move) { // cannot move in this terrain
779  color = "red";
780  } else if (m.movement_cost > 1) {
781  color = "yellow";
782  } else {
783  color = "white";
784  }
785  str << "<format>color=" << color << " text='";
786  // A 5 MP margin; if the movement costs go above
787  // the unit's max moves + 5, we replace it with dashes.
788  if(cannot_move && (m.movement_cost > type_.movement() + 5)) {
790  } else {
791  str << m.movement_cost;
792  }
793  str << "'</format>";
794  markup = str.str();
795  str.str(clear_stringstream);
796  str << m.movement_cost;
797  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
798 
799  //defense cap
800  if (has_terrain_defense_caps) {
801  str.str(clear_stringstream);
802  if (m.defense_cap) {
803  str << "<format>color='"<< color <<"' text='" << m.defense << "%'</format>";
804  } else {
805  str << "<format>color=white text='" << font::unicode_figure_dash << "'</format>";
806  }
807 
808  markup = str.str();
809  str.str(clear_stringstream);
810  if (m.defense_cap) {
811  str << m.defense << '%';
812  } else {
814  }
815  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
816  }
817 
818  //vision
819  if (has_vision) {
820  str.str(clear_stringstream);
821  const bool cannot_view = m.vision_cost > type_.vision();
822  if (cannot_view) { // cannot view in this terrain
823  color = "red";
824  } else if (m.vision_cost > m.movement_cost) {
825  color = "yellow";
826  } else if (m.vision_cost == m.movement_cost) {
827  color = "white";
828  } else {
829  color = "green";
830  }
831  str << "<format>color=" << color << " text='";
832  // A 5 MP margin; if the vision costs go above
833  // the unit's vision + 5, we replace it with dashes.
834  if(cannot_view && (m.vision_cost > type_.vision() + 5)) {
836  } else {
837  str << m.vision_cost;
838  }
839  str << "'</format>";
840  markup = str.str();
841  str.str(clear_stringstream);
842  str << m.vision_cost;
843  row.emplace_back(markup, font::pango_line_width(str.str(), normal_font_size));
844  }
845 
846  //jamming
847  if (has_jamming) {
848  str.str(clear_stringstream);
849  const bool cannot_jam = m.jamming_cost > type_.jamming();
850  if (cannot_jam) { // cannot jamm in this terrain
851  color = "red";
852  } else if (m.jamming_cost > m.vision_cost) {
853  color = "yellow";
854  } else if (m.jamming_cost == m.vision_cost) {
855  color = "white";
856  } else {
857  color = "green";
858  }
859  str << "<format>color=" << color << " text='";
860  // A 5 MP margin; if the jamming costs go above
861  // the unit's jamming + 5, we replace it with dashes.
862  if (cannot_jam && m.jamming_cost > type_.jamming() + 5) {
864  } else {
865  str << m.jamming_cost;
866  }
867  str << "'</format>";
868 
869  push_tab_pair(row, str.str());
870  }
871 
872  table.push_back(row);
873  }
874 
875  ss << generate_table(table);
876  } else {
877  WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.\n";
878  }
879  return ss.str();
880 }
881 
882 void unit_topic_generator::push_header(std::vector< item > &row, const std::string& name) const {
884 }
885 
886 } // end namespace help
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:292
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:285
const std::string ability_prefix
Definition: help_impl.cpp:96
bool operator<(const terrain_movement_info &other) const
const std::string unit_prefix
Definition: help_impl.cpp:90
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:1559
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:1262
std::map< std::string, t_string > string_map
static lg::log_domain log_help("help")
const std::string unknown_unit_topic
Definition: help_impl.cpp:89
const std::string & big_profile() const
Definition: types.hpp:182
virtual std::string operator()() const
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
logger & info()
Definition: log.cpp:89
const std::string & flag_rgb() const
Definition: types.cpp:723
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:50
unit_type_data unit_types
Definition: types.cpp:1481
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
static CVideo & get_singleton()
Definition: video.hpp:49
const int normal_font_size
Definition: help_impl.cpp:82
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:875
The basic "size" of the unit - flying, small land, large land, etc.
Definition: movetype.hpp:44
bool capped(const t_translation::terrain_code &terrain) const
Returns whether there is a defense cap associated to this terrain.
Definition: movetype.hpp:198
unsigned image_width(const std::string &filename)
Definition: help_impl.cpp:1549
static std::string _(const char *str)
Definition: gettext.hpp:93
const std::string terrain_prefix
Definition: help_impl.cpp:91
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:288
std::string bold(const std::string &s)
Definition: help_impl.hpp:402
A single unit type that the player may recruit.
Definition: types.hpp:45
bool is_nonnull() const
True if this object represents some sentinel values.
Definition: terrain.hpp:129
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
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:1254
static std::string best_str(bool best)
const t_string & editor_name() const
Definition: terrain.hpp:49
const std::string unicode_minus
Definition: constants.cpp:42
std::string generate_table(const table_spec &tab, const unsigned int spacing)
Definition: help_impl.cpp:1573
std::vector< std::vector< help::item > > table_spec
Definition: help_impl.hpp:412
bool hide_help() const
Definition: types.cpp:627
std::shared_ptr< terrain_type_data > load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1622
const terrain_code FOGGED
bool hide_if_impassable() const
Definition: terrain.hpp:63
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:141
terrain_defense & get_defense()
Definition: movetype.hpp:267
static const std::set< std::string > effects
The set of applicable effects for movement types.
Definition: movetype.hpp:344
virtual std::string operator()() const
const terrain_code MINUS
void push_header(std::vector< help::item > &row, const std::string &name) const
const terrain_code PLUS
std::set< t_translation::terrain_code > & encountered_terrains()
Definition: game.cpp:935
utils::string_map damage_table() const
Returns a map from attack types to resistances.
Definition: movetype.hpp:302
t_translation::ter_list::const_iterator ter_iter
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:144
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:297
const std::string unicode_figure_dash
Definition: constants.cpp:45
#define WRN_HP
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:206
static void print_trait_list(std::stringstream &ss, const std::vector< trait_data > &l)
static std::string gettext(const char *str)
Definition: gettext.hpp:60
std::size_t i
Definition: function.cpp:967
const std::string variation_prefix
Definition: help_impl.cpp:95
std::vector< std::string > variations() const
Definition: types.cpp:748
std::string resistance_color(const int resistance)
Definition: helper.cpp:43
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:382
static map_location::DIRECTION s
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:765
std::vector< std::string > names
Definition: build_info.cpp:66
std::string to_hex_string() const
Returns the stored color in rrggbb hex format.
Definition: color.cpp:98
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:29
const bool & debug
const std::string & small_profile() const
Definition: types.hpp:181
std::string escape(const std::string &s)
Prepend all chars with meaning inside attributes with a backslash.
Definition: help_impl.cpp:1616
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:282
const std::string nbsp
Definition: constants.cpp:40
const t_string & name() const
Definition: terrain.hpp:48
const std::string & image() const
Definition: types.hpp:179
std::string jump(const unsigned amount)
Definition: help_impl.hpp:395
bool empty() const
Definition: tstring.hpp:187
const ter_match ALL_OFF_MAP
const std::string weapon_numbers_sep
Definition: constants.cpp:49
double t
Definition: astarsearch.cpp:65
const unit_type & get_variation(const std::string &id) const
Definition: types.cpp:477
color_t red_to_green(int val, bool for_text)
Return a color corresponding to the value val red for val=0 to green for val=100, passing by yellow...
std::vector< std::string > split(const config_attribute_value &val)
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)
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
Definition: gettext.cpp:491
symbol_table string_table
Definition: language.cpp:65
const t_string & variation_name() const
Definition: types.hpp:177
Standard logging facilities (interface).
const std::string & id() const
Definition: terrain.hpp:52
std::vector< terrain_code > ter_list
Definition: translation.hpp:78
const unit_type & get_gender_unit_type(std::string gender) const
Returns a gendered variant of this unit_type.
Definition: types.cpp:456
EXIT_STATUS start(const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
Definition: editor_main.cpp:30
static void reverse(lua_State *L, StkId from, StkId to)
Definition: lapi.cpp:203
Definition: help.cpp:57
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
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:1107
Transitional API for porting SDL_ttf-based code to Pango.
std::pair< std::string, std::string > trait_data
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
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:863
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.