The Battle for Wesnoth  1.15.0-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 
16 
17 #include "font/sdl_ttf.hpp" // for line_width
18 #include "game_config.hpp" // for debug, menu_contract, etc
19 #include "preferences/game.hpp" // for encountered_terrains, etc
20 #include "gettext.hpp" // for _, gettext, N_
21 #include "language.hpp" // for string_table, symbol_table
22 #include "log.hpp" // for LOG_STREAM, logger, etc
23 #include "movetype.hpp" // for movetype, movetype::effects, etc
24 #include "units/race.hpp" // for unit_race, etc
25 #include "terrain/terrain.hpp" // for terrain_type
26 #include "terrain/translation.hpp" // for operator==, ter_list, etc
27 #include "terrain/type_data.hpp" // for terrain_type_data, etc
28 #include "tstring.hpp" // for t_string, operator<<
29 #include "units/helper.hpp" // for resistance_color
30 #include "units/types.hpp" // for unit_type, unit_type_data, etc
31 #include "video.hpp" // fore current_resolution
32 
33 #include <boost/optional.hpp> // for optional
34 #include <iostream> // for operator<<, basic_ostream, etc
35 #include <map> // for map, etc
36 #include <set>
37 #include <SDL.h>
38 
39 static lg::log_domain log_help("help");
40 #define WRN_HP LOG_STREAM(warn, log_help)
41 #define DBG_HP LOG_STREAM(debug, log_help)
42 
43 namespace help {
44 
46 {
47  const t_string name;
48  const t_string id;
49  const int defense;
50  const int movement_cost;
51  const int vision_cost;
52  const int jamming_cost;
53  const bool defense_cap;
54 
55  bool operator<(const terrain_movement_info& other) const
56  {
57  return translation::icompare(name, other.name) < 0;
58  }
59 };
60 
61 static std::string best_str(bool best) {
62  std::string lang_policy = (best ? _("Best of") : _("Worst of"));
63  std::string color_policy = (best ? "green": "red");
64 
65  return "<format>color='" + color_policy + "' text='" + lang_policy + "'</format>";
66 }
67 
68 typedef t_translation::ter_list::const_iterator ter_iter;
69 // Gets an english description of a terrain ter_list alias behavior: "Best of cave, hills", "Worst of Swamp, Forest" etc.
70 static std::string print_behavior_description(ter_iter start, ter_iter end, const ter_data_cache & tdata, bool first_level = true, bool begin_best = true)
71 {
72 
73  if (start == end) return "";
74  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.
75 
76  boost::optional<ter_iter> last_change_pos;
77 
78  bool best = begin_best;
79  for (ter_iter i = start; i != end; ++i) {
80  if ((best && *i == t_translation::MINUS) || (!best && *i == t_translation::PLUS)) {
81  best = !best;
82  last_change_pos = i;
83  }
84  }
85 
86  std::stringstream ss;
87 
88  if (!last_change_pos) {
89  std::vector<std::string> names;
90  for (ter_iter i = start; i != end; ++i) {
91  const terrain_type tt = tdata->get_terrain_info(*i);
92  if (!tt.editor_name().empty())
93  names.push_back(tt.editor_name());
94  }
95 
96  if (names.empty()) return "";
97  if (names.size() == 1) return names.at(0);
98 
99  ss << best_str(best) << " ";
100  if (!first_level) ss << "( ";
101  ss << names.at(0);
102 
103  for (std::size_t i = 1; i < names.size(); i++) {
104  ss << ", " << names.at(i);
105  }
106 
107  if (!first_level) ss << " )";
108  } else {
109  std::vector<std::string> names;
110  for (ter_iter i = *last_change_pos+1; i != end; ++i) {
111  const terrain_type tt = tdata->get_terrain_info(*i);
112  if (!tt.editor_name().empty())
113  names.push_back(tt.editor_name());
114  }
115 
116  if (names.empty()) { //This alias list is apparently padded with junk at the end, so truncate it without adding more parens
117  return print_behavior_description(start, *last_change_pos, tdata, first_level, begin_best);
118  }
119 
120  ss << best_str(best) << " ";
121  if (!first_level) ss << "( ";
122  ss << print_behavior_description(start, *last_change_pos-1, tdata, false, begin_best);
123  // Printed the (parenthesized) leading part from before the change, now print the remaining names in this group.
124  for (const std::string & s : names) {
125  ss << ", " << s;
126  }
127  if (!first_level) ss << " )";
128  }
129  return ss.str();
130 }
131 
133  std::stringstream ss;
134 
135  if (!type_.icon_image().empty())
136  ss << "<img>src='images/buttons/icon-base-32.png~RC(magenta>" << type_.id()
137  << ")~BLIT("<< "terrain/" << type_.icon_image() << "_30.png)" << "'</img> ";
138 
139  if (!type_.editor_image().empty())
140  ss << "<img>src='" << type_.editor_image() << "'</img> ";
141 
142  if (!type_.help_topic_text().empty())
143  ss << "\n\n" << type_.help_topic_text().str() << "\n";
144  else
145  ss << "\n";
146 
148 
149  if (!tdata) {
150  WRN_HP << "When building terrain help topics, we couldn't acquire any terrain types data\n";
151  return ss.str();
152  }
153 
154  if (!(type_.union_type().size() == 1 && type_.union_type()[0] == type_.number() && type_.is_nonnull())) {
155 
156  const t_translation::ter_list& underlying_mvt_terrains = tdata->underlying_mvt_terrain(type_.number());
157 
158  ss << "\n" << _("Base Terrain: ");
159 
160  bool first = true;
161  for (const t_translation::terrain_code& underlying_terrain : underlying_mvt_terrains) {
162  const terrain_type& mvt_base = tdata->get_terrain_info(underlying_terrain);
163 
164  if (mvt_base.editor_name().empty()) continue;
165 
166  if (!first) {
167  ss << ", ";
168  } else {
169  first = false;
170  }
171 
172  ss << make_link(mvt_base.editor_name(), ".." + terrain_prefix + mvt_base.id());
173  }
174 
175  ss << "\n";
176 
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 = tdata->underlying_def_terrain(type_.number());
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::line_width(traits_label+must_have_count.str(), normal_font_size)
448  - font::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  for(auto iter = type_.abilities_metadata().begin(); iter != type_.abilities_metadata().end(); ++iter) {
475  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
476 
477  std::string lang_ability = translation::gettext(iter->name.c_str());
478  ss << make_link(lang_ability, ref_id);
479 
480  if(std::next(iter) != type_.abilities_metadata().end()) {
481  ss << ", ";
482  }
483  }
484 
485  ss << "\n\n";
486  }
487 
488  // Print the extra AMLA upgrade abilities, cross-reference them to their respective topics.
489  if(!type_.adv_abilities_metadata().empty()) {
490  ss << _("Ability Upgrades: ");
491 
492  for(auto iter = type_.adv_abilities_metadata().begin(); iter != type_.adv_abilities_metadata().end(); ++iter) {
493  const std::string ref_id = ability_prefix + iter->id + iter->name.base_str();
494 
495  std::string lang_ability = translation::gettext(iter->name.c_str());
496  ss << make_link(lang_ability, ref_id);
497 
498  if(std::next(iter) != type_.adv_abilities_metadata().end()) {
499  ss << ", ";
500  }
501  }
502 
503  ss << "\n\n";
504  }
505 
506  // Print some basic information such as HP and movement points.
507  // TODO: Make this info update according to musthave traits, similar to movetype below.
508 
509  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
510  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
511  // unpleasant line breaks (issue #3256).
512  ss << _("HP:") << font::nbsp << type_.hitpoints() << jump(30)
513  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
514  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
515  // unpleasant line breaks (issue #3256).
516  << _("Moves:") << font::nbsp << type_.movement() << jump(30);
517  if (type_.vision() != type_.movement()) {
518  // TRANSLATORS: This string is used in the help page of a single unit. If the translation
519  // uses spaces, use non-breaking spaces as appropriate for the target language to prevent
520  // unpleasant line breaks (issue #3256).
521  ss << _("Vision:") << font::nbsp << type_.vision() << jump(30);
522  }
523  if (type_.jamming() > 0) {
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 << _("Jamming:") << font::nbsp << type_.jamming() << jump(30);
528  }
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  ss << _("Cost:") << font::nbsp << type_.cost() << jump(30)
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  << _("Alignment:") << font::nbsp
537  << make_link(type_.alignment_description(type_.alignment(), type_.genders().front()), "time_of_day")
538  << jump(30);
539  if (type_.can_advance() || type_.modification_advancements()) {
540  // TRANSLATORS: This string is used in the help page of a single unit. It uses
541  // non-breaking spaces to prevent unpleasant line breaks (issue #3256). In the
542  // translation use non-breaking spaces as appropriate for the target language.
543  ss << _("Required\u00a0XP:") << font::nbsp << type_.experience_needed();
544  }
545 
546  // Print the detailed description about the unit.
547  ss << "\n\n" << detailed_description;
548 
549  // Print the different attacks a unit has, if it has any.
550  if (!type_.attacks().empty()) {
551  // Print headers for the table.
552  ss << "\n\n<header>text='" << escape(_("unit help^Attacks"))
553  << "'</header>\n\n";
554  table_spec table;
555 
556  std::vector<item> first_row;
557  // Dummy element, icons are below.
558  first_row.push_back(item("", 0));
559  push_header(first_row, _("unit help^Name"));
560  push_header(first_row, _("Type"));
561  push_header(first_row, _("Strikes"));
562  push_header(first_row, _("Range"));
563  push_header(first_row, _("Special"));
564  table.push_back(first_row);
565  // Print information about every attack.
566  for(const attack_type& attack : type_.attacks()) {
567  std::string lang_weapon = attack.name();
568  std::string lang_type = string_table["type_" + attack.type()];
569  std::vector<item> row;
570  std::stringstream attack_ss;
571  attack_ss << "<img>src='" << attack.icon() << "'</img>";
572  row.emplace_back(attack_ss.str(),image_width(attack.icon()));
573  push_tab_pair(row, lang_weapon);
574  push_tab_pair(row, lang_type);
575  attack_ss.str(clear_stringstream);
576  attack_ss << attack.damage() << font::weapon_numbers_sep << attack.num_attacks()
577  << " " << attack.accuracy_parry_description();
578  push_tab_pair(row, attack_ss.str());
579  attack_ss.str(clear_stringstream);
580  if (attack.min_range() > 1 || attack.max_range() > 1) {
581  attack_ss << attack.min_range() << "-" << attack.max_range() << ' ';
582  }
583  attack_ss << string_table["range_" + attack.range()];
584  push_tab_pair(row, attack_ss.str());
585  attack_ss.str(clear_stringstream);
586  // Show this attack's special, if it has any. Cross
587  // reference it to the section describing the special.
588  std::vector<std::pair<t_string, t_string>> specials = attack.special_tooltips();
589  if (!specials.empty()) {
590  std::string lang_special = "";
591  const std::size_t specials_size = specials.size();
592  for (std::size_t i = 0; i != specials_size; ++i) {
593  const std::string ref_id = std::string("weaponspecial_")
594  + specials[i].first.base_str();
595  lang_special = (specials[i].first);
596  attack_ss << make_link(lang_special, ref_id);
597  if (i+1 != specials_size) {
598  attack_ss << ", "; //comma placed before next special
599  }
600  }
601  row.emplace_back(attack_ss.str(), font::line_width(lang_special, normal_font_size));
602  }
603  table.push_back(row);
604  }
605  ss << generate_table(table);
606  }
607 
608  // Generate the movement type of the unit, with resistance, defense, movement, jamming and vision data updated according to any 'musthave' traits which always apply
609  movetype movement_type = type_.movement_type();
610  config::const_child_itors traits = type_.possible_traits();
611  if (!traits.empty() && type_.num_traits() > 0) {
612  for (const config & t : traits) {
613  if (t["availability"].str() == "musthave") {
614  for (const config & effect : t.child_range("effect")) {
615  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.
616  && movetype::effects.find(effect["apply_to"].str()) != movetype::effects.end()) {
617  movement_type.merge(effect, effect["replace"].to_bool());
618  }
619  }
620  }
621  }
622  }
623 
624  // Print the resistance table of the unit.
625  ss << "\n\n<header>text='" << escape(_("Resistances"))
626  << "'</header>\n\n";
627  table_spec resistance_table;
628  std::vector<item> first_res_row;
629  push_header(first_res_row, _("Attack Type"));
630  push_header(first_res_row, _("Resistance"));
631  resistance_table.push_back(first_res_row);
632  utils::string_map dam_tab = movement_type.damage_table();
633  for(std::pair<std::string, std::string> dam_it : dam_tab) {
634  std::vector<item> row;
635  int resistance = 100;
636  try {
637  resistance -= std::stoi(dam_it.second);
638  } catch(std::invalid_argument&) {}
639  std::string resist = std::to_string(resistance) + '%';
640  const std::size_t pos = resist.find('-');
641  if (pos != std::string::npos) {
642  resist.replace(pos, 1, font::unicode_minus);
643  }
644  std::string color = unit_helper::resistance_color(resistance);
645  std::string lang_weapon = string_table["type_" + dam_it.first];
646  push_tab_pair(row, lang_weapon);
647  std::stringstream str;
648  str << "<format>color=\"" << color << "\" text='"<< resist << "'</format>";
649  const std::string markup = str.str();
650  str.str(clear_stringstream);
651  str << resist;
652  row.emplace_back(markup, font::line_width(str.str(), normal_font_size));
653  resistance_table.push_back(row);
654  }
655  ss << generate_table(resistance_table);
656 
657  if (ter_data_cache tdata = load_terrain_types_data()) {
658  // Print the terrain modifier table of the unit.
659  ss << "\n\n<header>text='" << escape(_("Terrain Modifiers"))
660  << "'</header>\n\n";
661  std::vector<item> first_row;
662  table_spec table;
663  push_header(first_row, _("Terrain"));
664  push_header(first_row, _("Defense"));
665  push_header(first_row, _("Movement Cost"));
666 
667  const bool has_terrain_defense_caps = movement_type.has_terrain_defense_caps(preferences::encountered_terrains());
668  if (has_terrain_defense_caps) {
669  push_header(first_row, _("Defense Cap"));
670  }
671 
672  const bool has_vision = type_.movement_type().has_vision_data();
673  if (has_vision) {
674  push_header(first_row, _("Vision Cost"));
675  }
676  const bool has_jamming = type_.movement_type().has_jamming_data();
677  if (has_jamming) {
678  push_header(first_row, _("Jamming Cost"));
679  }
680 
681  table.push_back(first_row);
682 
683  std::set<terrain_movement_info> terrain_moves;
684 
687  continue;
688  }
689  const terrain_type& info = tdata->get_terrain_info(terrain);
690  const int moves = movement_type.movement_cost(terrain);
691  const bool cannot_move = moves > type_.movement();
692  if (cannot_move && info.hide_if_impassable()) {
693  continue;
694  }
695 
696  if (info.union_type().size() == 1 && info.union_type()[0] == info.number() && info.is_nonnull()) {
697  terrain_movement_info movement_info =
698  {
699  info.name(),
700  info.id(),
701  100 - movement_type.defense_modifier(terrain),
702  moves,
703  movement_type.vision_cost(terrain),
704  movement_type.jamming_cost(terrain),
705  movement_type.get_defense().capped(terrain)
706  };
707 
708  terrain_moves.insert(movement_info);
709  }
710  }
711 
712  for(const terrain_movement_info &m : terrain_moves)
713  {
714  std::vector<item> row;
715 
716  bool high_res = false;
717  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
718  const std::string terrain_image = "icons/terrain/terrain_type_" + m.id + (high_res ? "_30.png" : ".png");
719 
720  const std::string final_image = tc_base + "~RC(magenta>" + m.id + ")~BLIT(" + terrain_image + ")";
721 
722  row.emplace_back("<img>src='" + final_image + "'</img> " +
723  make_link(m.name, "..terrain_" + m.id),
724  font::line_width(m.name, normal_font_size) + (high_res ? 32 : 16) );
725 
726  //defense - range: +10 % .. +70 %
727  std::string color;
728  if (m.defense <= 10) {
729  color = "red";
730  } else if (m.defense <= 30) {
731  color = "yellow";
732  } else if (m.defense <= 50) {
733  color = "white";
734  } else {
735  color = "green";
736  }
737 
738  std::stringstream str;
739  str << "<format>color=" << color << " text='"<< m.defense << "%'</format>";
740  std::string markup = str.str();
741  str.str(clear_stringstream);
742  str << m.defense << "%";
743  row.emplace_back(markup, font::line_width(str.str(), normal_font_size));
744 
745  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
746  str.str(clear_stringstream);
747  bool cannot_move = m.movement_cost > type_.movement();
748  if (cannot_move) { // cannot move in this terrain
749  color = "red";
750  } else if (m.movement_cost > 1) {
751  color = "yellow";
752  } else {
753  color = "white";
754  }
755  str << "<format>color=" << color << " text='";
756  // A 5 MP margin; if the movement costs go above
757  // the unit's max moves + 5, we replace it with dashes.
758  if(cannot_move && (m.movement_cost > type_.movement() + 5)) {
760  } else {
761  str << m.movement_cost;
762  }
763  str << "'</format>";
764  markup = str.str();
765  str.str(clear_stringstream);
766  str << m.movement_cost;
767  row.emplace_back(markup, font::line_width(str.str(), normal_font_size));
768 
769  //defense cap
770  if (has_terrain_defense_caps) {
771  str.str(clear_stringstream);
772  if (m.defense_cap) {
773  str << "<format>color='"<< color <<"' text='" << m.defense << "%'</format>";
774  } else {
775  str << "<format>color=white text='" << font::unicode_figure_dash << "'</format>";
776  }
777 
778  markup = str.str();
779  str.str(clear_stringstream);
780  if (m.defense_cap) {
781  str << m.defense << '%';
782  } else {
784  }
785  row.emplace_back(markup, font::line_width(str.str(), normal_font_size));
786  }
787 
788  //vision
789  if (has_vision) {
790  str.str(clear_stringstream);
791  const bool cannot_view = m.vision_cost > type_.vision();
792  if (cannot_view) { // cannot view in this terrain
793  color = "red";
794  } else if (m.vision_cost > m.movement_cost) {
795  color = "yellow";
796  } else if (m.vision_cost == m.movement_cost) {
797  color = "white";
798  } else {
799  color = "green";
800  }
801  str << "<format>color=" << color << " text='";
802  // A 5 MP margin; if the vision costs go above
803  // the unit's vision + 5, we replace it with dashes.
804  if(cannot_view && (m.vision_cost > type_.vision() + 5)) {
806  } else {
807  str << m.vision_cost;
808  }
809  str << "'</format>";
810  markup = str.str();
811  str.str(clear_stringstream);
812  str << m.vision_cost;
813  row.emplace_back(markup, font::line_width(str.str(), normal_font_size));
814  }
815 
816  //jamming
817  if (has_jamming) {
818  str.str(clear_stringstream);
819  const bool cannot_jam = m.jamming_cost > type_.jamming();
820  if (cannot_jam) { // cannot jamm in this terrain
821  color = "red";
822  } else if (m.jamming_cost > m.vision_cost) {
823  color = "yellow";
824  } else if (m.jamming_cost == m.vision_cost) {
825  color = "white";
826  } else {
827  color = "green";
828  }
829  str << "<format>color=" << color << " text='";
830  // A 5 MP margin; if the jamming costs go above
831  // the unit's jamming + 5, we replace it with dashes.
832  if (cannot_jam && m.jamming_cost > type_.jamming() + 5) {
834  } else {
835  str << m.jamming_cost;
836  }
837  str << "'</format>";
838 
839  push_tab_pair(row, str.str());
840  }
841 
842  table.push_back(row);
843  }
844 
845  ss << generate_table(table);
846  } else {
847  WRN_HP << "When building unit help topics, the display object was null and we couldn't get the terrain info we need.\n";
848  }
849  return ss.str();
850 }
851 
852 void unit_topic_generator::push_header(std::vector< item > &row, const std::string& name) const {
853  row.emplace_back(bold(name), font::line_width(name, normal_font_size, TTF_STYLE_BOLD));
854 }
855 
856 } // end namespace help
int defense_modifier(const t_translation::terrain_code &terrain) const
Returns the defensive value of the indicated terrain.
Definition: movetype.hpp:207
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:200
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
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:1275
std::map< std::string, t_string > string_map
static lg::log_domain log_help("help")
void push_header(std::vector< item > &row, const std::string &name) const
const std::string unknown_unit_topic
Definition: help_impl.cpp:88
const std::string & big_profile() const
Definition: types.hpp:165
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:90
const std::string & flag_rgb() const
Definition: types.cpp:744
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:1452
static std::string print_behavior_description(ter_iter start, ter_iter end, const ter_data_cache &tdata, bool first_level=true, bool begin_best=true)
static CVideo & get_singleton()
Definition: video.hpp:43
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.
Definition: movetype.cpp:750
The basic "size" of the unit - flying, small land, large land, etc.
Definition: movetype.hpp:26
bool capped(const t_translation::terrain_code &terrain) const
Returns whether there is a defense cap associated to this terrain.
Definition: movetype.hpp:132
unsigned image_width(const std::string &filename)
Definition: help_impl.cpp:1470
const std::string terrain_prefix
Definition: help_impl.cpp:90
-file util.hpp
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
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:203
std::string bold(const std::string &s)
Definition: help_impl.hpp:379
A single unit type that the player may recruit.
Definition: types.hpp:42
bool is_nonnull() const
Definition: terrain.hpp:52
const terrain_code VOID_TERRAIN
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.hpp:372
static std::string best_str(bool best)
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
const t_string & editor_name() const
Definition: terrain.hpp:34
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:1485
bool hide_help() const
Definition: types.cpp:587
const terrain_code FOGGED
bool hide_if_impassable() const
Definition: terrain.hpp:41
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:136
terrain_defense & get_defense()
Definition: movetype.hpp:182
std::vector< std::vector< std::pair< std::string, unsigned int > > > table_spec
Definition: help_impl.hpp:386
static const std::set< std::string > effects
The set of applicable effects for movement types.
Definition: movetype.hpp:231
virtual std::string operator()() const
const terrain_code MINUS
const terrain_code PLUS
std::set< t_translation::terrain_code > & encountered_terrains()
Definition: game.cpp:944
utils::string_map damage_table() const
Returns a map from attack types to resistances.
Definition: movetype.hpp:217
t_translation::ter_list::const_iterator ter_iter
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:139
ter_data_cache load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1534
int get_width(bool as_pixels=true) const
Returns the window renderer width in pixels or screen coordinates.
Definition: video.cpp:320
static UNUSEDNOWARN std::string gettext(const char *str)
Definition: gettext.hpp:66
static const ::config * terrain
The terrain used to create the cache.
Definition: minimap.cpp:130
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:238
static void print_trait_list(std::stringstream &ss, const std::vector< trait_data > &l)
std::size_t i
Definition: function.cpp:933
const std::string variation_prefix
Definition: help_impl.cpp:94
std::pair< std::string, unsigned > item
std::vector< std::string > variations() const
Definition: types.cpp:769
std::string resistance_color(const int resistance)
Definition: helper.cpp:35
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:359
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:786
t_translation::terrain_code number() const
Definition: terrain.hpp:44
std::vector< std::string > names
Definition: build_info.cpp:56
const t_translation::ter_list & union_type() const
Definition: terrain.hpp:50
unsigned screen_width
The screen resolution should be available for all widgets since their drawing method will depend on i...
Definition: settings.cpp:25
const bool & debug
const std::string & small_profile() const
Definition: types.hpp:164
std::string escape(const std::string &s)
Prepend all chars with meaning inside attributes with a backslash.
Definition: help_impl.cpp:1528
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:197
const std::string nbsp
Definition: constants.cpp:36
const t_string & name() const
Definition: terrain.hpp:33
#define next(ls)
Definition: llex.cpp:32
const std::string & image() const
Definition: types.hpp:162
std::string jump(const unsigned amount)
Definition: help_impl.hpp:372
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:466
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:63
Standard logging facilities (interface).
const std::string & id() const
Definition: terrain.hpp:37
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:445
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:56
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
UNIT_DESCRIPTION_TYPE description_type(const unit_type &type)
Return the type of description that should be shown for a unit of the given kind. ...
Definition: help_impl.cpp:1004
const std::string & variation_name() const
Definition: types.hpp:160
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:416
std::pair< std::string, std::string > trait_data
void push_tab_pair(std::vector< std::pair< std::string, unsigned int >> &v, const std::string &s)
Definition: help_impl.cpp:1480
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:738
std::shared_ptr< terrain_type_data > ter_data_cache