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