The Battle for Wesnoth  1.17.0-dev
help_impl.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 #include "help/help_impl.hpp"
17 
18 #include "about.hpp" // for get_text
19 #include "actions/attack.hpp" // for time_of_day bonus
20 #include "display.hpp" // for display
21 #include "display_context.hpp" // for display_context
22 #include "formula/string_utils.hpp" // for VGETTEXT
23 #include "game_config.hpp" // for debug, menu_contract, etc
24 #include "game_config_manager.hpp" // for game_config_manager
25 #include "preferences/game.hpp" // for encountered_terrains, etc
26 #include "gettext.hpp" // for _, gettext, N_
28 #include "hotkey/hotkey_command.hpp" // for is_scope_active, etc
29 #include "picture.hpp" // for get_image, locator
30 #include "log.hpp" // for LOG_STREAM, logger, etc
31 #include "utils/make_enum.hpp" // for operator<<
32 #include "map/map.hpp" // for gamemap
33 #include "font/standard_colors.hpp" // for NORMAL_COLOR
34 #include "font/sdl_ttf_compat.hpp"
35 #include "units/race.hpp" // for unit_race, etc
36 #include "resources.hpp" // for tod_manager, config_manager
37 #include "sdl/surface.hpp" // for surface
38 #include "serialization/string_utils.hpp" // for split, quoted_split, etc
39 #include "serialization/unicode_cast.hpp" // for unicode_cast
40 #include "serialization/utf8_exception.hpp" // for char_t, etc
41 #include "terrain/terrain.hpp" // for terrain_type
42 #include "terrain/translation.hpp" // for operator==, ter_list, etc
43 #include "terrain/type_data.hpp" // for terrain_type_data, etc
44 #include "time_of_day.hpp" // for time_of_day
45 #include "tod_manager.hpp" // for tod_manager
46 #include "tstring.hpp" // for t_string, operator<<
47 #include "units/types.hpp" // for unit_type, unit_type_data, etc
48 #include "serialization/unicode.hpp" // for iterator
49 #include "color.hpp"
50 
51 #include <cassert> // for assert
52 #include <algorithm> // for sort, find, transform, etc
53 #include <iostream> // for operator<<, basic_ostream, etc
54 #include <iterator> // for back_insert_iterator, etc
55 #include <map> // for map, etc
56 #include <set>
57 
58 static lg::log_domain log_display("display");
59 #define WRN_DP LOG_STREAM(warn, log_display)
60 
61 static lg::log_domain log_help("help");
62 #define WRN_HP LOG_STREAM(warn, log_help)
63 #define DBG_HP LOG_STREAM(debug, log_help)
64 
65 namespace help {
66 
67 const game_config_view *game_cfg = nullptr;
68 // The default toplevel.
70 // All sections and topics not referenced from the default toplevel.
72 
75 boost::tribool last_debug_state = boost::indeterminate;
76 
77 std::vector<std::string> empty_string_vector;
78 const int max_section_level = 15;
81 const int box_width = 2;
83 const unsigned max_history = 100;
84 const std::string topic_img = "help/topic.png";
85 const std::string closed_section_img = "help/closed_section.png";
86 const std::string open_section_img = "help/open_section.png";
87 // The topic to open by default when opening the help dialog.
88 const std::string default_show_topic = "..introduction";
89 const std::string unknown_unit_topic = ".unknown_unit";
90 const std::string unit_prefix = "unit_";
91 const std::string terrain_prefix = "terrain_";
92 const std::string race_prefix = "race_";
93 const std::string faction_prefix = "faction_";
94 const std::string era_prefix = "era_";
95 const std::string variation_prefix = "variation_";
96 const std::string ability_prefix = "ability_";
97 
98 static bool is_cjk_char(const char32_t ch)
99 {
100  /**
101  * You can check these range at http://unicode.org/charts/
102  * see the "East Asian Scripts" part.
103  * Notice that not all characters in that part is still in use today, so don't list them all here.
104  * Below are characters that I guess may be used in wesnoth translations.
105  */
106 
107  //FIXME add range from Japanese-specific and Korean-specific section if you know the characters are used today.
108 
109  if (ch < 0x2e80) return false; // shortcut for common non-CJK
110 
111  return
112  //Han Ideographs: all except Supplement
113  (ch >= 0x4e00 && ch < 0x9fcf) ||
114  (ch >= 0x3400 && ch < 0x4dbf) ||
115  (ch >= 0x20000 && ch < 0x2a6df) ||
116  (ch >= 0xf900 && ch < 0xfaff) ||
117  (ch >= 0x3190 && ch < 0x319f) ||
118 
119  //Radicals: all except Ideographic Description
120  (ch >= 0x2e80 && ch < 0x2eff) ||
121  (ch >= 0x2f00 && ch < 0x2fdf) ||
122  (ch >= 0x31c0 && ch < 0x31ef) ||
123 
124  //Chinese-specific: Bopomofo and Bopomofo Extended
125  (ch >= 0x3104 && ch < 0x312e) ||
126  (ch >= 0x31a0 && ch < 0x31bb) ||
127 
128  //Yi-specific: Yi Radicals, Yi Syllables
129  (ch >= 0xa490 && ch < 0xa4c7) ||
130  (ch >= 0xa000 && ch < 0xa48d) ||
131 
132  //Japanese-specific: Hiragana, Katakana, Kana Supplement
133  (ch >= 0x3040 && ch <= 0x309f) ||
134  (ch >= 0x30a0 && ch <= 0x30ff) ||
135  (ch >= 0x1b000 && ch <= 0x1b001) ||
136 
137  //Ainu-specific: Katakana Phonetic Extensions
138  (ch >= 0x31f0 && ch <= 0x31ff) ||
139 
140  //Korean-specific: Hangul Syllables, Hangul Jamo, Hangul Jamo Extended-A, Hangul Jamo Extended-B
141  (ch >= 0xac00 && ch < 0xd7af) ||
142  (ch >= 0x1100 && ch <= 0x11ff) ||
143  (ch >= 0xa960 && ch <= 0xa97c) ||
144  (ch >= 0xd7b0 && ch <= 0xd7fb) ||
145 
146  //CJK Symbols and Punctuation
147  (ch >= 0x3000 && ch < 0x303f) ||
148 
149  //Halfwidth and Fullwidth Forms
150  (ch >= 0xff00 && ch < 0xffef);
151 }
152 
153 bool section_is_referenced(const std::string &section_id, const config &cfg)
154 {
155  if (const config &toplevel = cfg.child("toplevel"))
156  {
157  const std::vector<std::string> toplevel_refs
158  = utils::quoted_split(toplevel["sections"]);
159  if (std::find(toplevel_refs.begin(), toplevel_refs.end(), section_id)
160  != toplevel_refs.end()) {
161  return true;
162  }
163  }
164 
165  for (const config &section : cfg.child_range("section"))
166  {
167  const std::vector<std::string> sections_refd
168  = utils::quoted_split(section["sections"]);
169  if (std::find(sections_refd.begin(), sections_refd.end(), section_id)
170  != sections_refd.end()) {
171  return true;
172  }
173  }
174  return false;
175 }
176 
177 bool topic_is_referenced(const std::string &topic_id, const config &cfg)
178 {
179  if (const config &toplevel = cfg.child("toplevel"))
180  {
181  const std::vector<std::string> toplevel_refs
182  = utils::quoted_split(toplevel["topics"]);
183  if (std::find(toplevel_refs.begin(), toplevel_refs.end(), topic_id)
184  != toplevel_refs.end()) {
185  return true;
186  }
187  }
188 
189  for (const config &section : cfg.child_range("section"))
190  {
191  const std::vector<std::string> topics_refd
192  = utils::quoted_split(section["topics"]);
193  if (std::find(topics_refd.begin(), topics_refd.end(), topic_id)
194  != topics_refd.end()) {
195  return true;
196  }
197  }
198  return false;
199 }
200 
201 void parse_config_internal(const config *help_cfg, const config *section_cfg,
202  section &sec, int level)
203 {
204  if (level > max_section_level) {
205  std::cerr << "Maximum section depth has been reached. Maybe circular dependency?"
206  << std::endl;
207  }
208  else if (section_cfg != nullptr) {
209  const std::vector<std::string> sections = utils::quoted_split((*section_cfg)["sections"]);
210  std::string id = level == 0 ? "toplevel" : (*section_cfg)["id"].str();
211  if (level != 0) {
212  if (!is_valid_id(id)) {
213  std::stringstream ss;
214  ss << "Invalid ID, used for internal purpose: '" << id << "'";
215  throw parse_error(ss.str());
216  }
217  }
218  std::string title = level == 0 ? "" : (*section_cfg)["title"].str();
219  sec.id = id;
220  sec.title = title;
221  std::vector<std::string>::const_iterator it;
222  // Find all child sections.
223  for (it = sections.begin(); it != sections.end(); ++it) {
224  if (const config &child_cfg = help_cfg->find_child("section", "id", *it))
225  {
226  section child_section;
227  parse_config_internal(help_cfg, &child_cfg, child_section, level + 1);
228  sec.add_section(child_section);
229  }
230  else {
231  std::stringstream ss;
232  ss << "Help-section '" << *it << "' referenced from '"
233  << id << "' but could not be found.";
234  throw parse_error(ss.str());
235  }
236  }
237 
238  generate_sections(help_cfg, (*section_cfg)["sections_generator"], sec, level);
239  if ((*section_cfg)["sort_sections"] == "yes") {
240  sec.sections.sort(section_less());
241  }
242 
243  bool sort_topics = false;
244  bool sort_generated = true;
245 
246  if ((*section_cfg)["sort_topics"] == "yes") {
247  sort_topics = true;
248  sort_generated = false;
249  } else if ((*section_cfg)["sort_topics"] == "no") {
250  sort_topics = false;
251  sort_generated = false;
252  } else if ((*section_cfg)["sort_topics"] == "generated") {
253  sort_topics = false;
254  sort_generated = true;
255  } else if (!(*section_cfg)["sort_topics"].empty()) {
256  std::stringstream ss;
257  ss << "Invalid sort option: '" << (*section_cfg)["sort_topics"] << "'";
258  throw parse_error(ss.str());
259  }
260 
261  std::vector<topic> generated_topics =
262  generate_topics(sort_generated,(*section_cfg)["generator"]);
263 
264  const std::vector<std::string> topics_id = utils::quoted_split((*section_cfg)["topics"]);
265  std::vector<topic> topics;
266 
267  // Find all topics in this section.
268  for (it = topics_id.begin(); it != topics_id.end(); ++it) {
269  if (const config &topic_cfg = help_cfg->find_child("topic", "id", *it))
270  {
271  std::string text = topic_cfg["text"];
272  text += generate_topic_text(topic_cfg["generator"], help_cfg, sec, generated_topics);
273  topic child_topic(topic_cfg["title"], topic_cfg["id"], text);
274  if (!is_valid_id(child_topic.id)) {
275  std::stringstream ss;
276  ss << "Invalid ID, used for internal purpose: '" << id << "'";
277  throw parse_error(ss.str());
278  }
279  topics.push_back(child_topic);
280  }
281  else {
282  std::stringstream ss;
283  ss << "Help-topic '" << *it << "' referenced from '" << id
284  << "' but could not be found." << std::endl;
285  throw parse_error(ss.str());
286  }
287  }
288 
289  if (sort_topics) {
290  std::sort(topics.begin(),topics.end(), title_less());
291  std::sort(generated_topics.begin(),
292  generated_topics.end(), title_less());
293  std::merge(generated_topics.begin(),
294  generated_topics.end(),topics.begin(),topics.end()
295  ,std::back_inserter(sec.topics),title_less());
296  }
297  else {
298  sec.topics.insert(sec.topics.end(),
299  topics.begin(), topics.end());
300  sec.topics.insert(sec.topics.end(),
301  generated_topics.begin(), generated_topics.end());
302  }
303  }
304 }
305 
307 {
308  section sec;
309  if (cfg != nullptr) {
310  const config& toplevel_cfg = cfg->child("toplevel");
311  parse_config_internal(cfg, toplevel_cfg ? &toplevel_cfg : nullptr, sec);
312  }
313  return sec;
314 }
315 
316 std::vector<topic> generate_topics(const bool sort_generated,const std::string &generator)
317 {
318  std::vector<topic> res;
319  if (generator.empty()) {
320  return res;
321  }
322 
323  if (generator == "abilities") {
324  res = generate_ability_topics(sort_generated);
325  } else if (generator == "weapon_specials") {
326  res = generate_weapon_special_topics(sort_generated);
327  } else if (generator == "time_of_days") {
328  res = generate_time_of_day_topics(sort_generated);
329  } else if (generator == "traits") {
330  res = generate_trait_topics(sort_generated);
331  } else {
332  std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
333  if (parts.size() > 1 && parts[0] == "units") {
334  res = generate_unit_topics(sort_generated, parts[1]);
335  } else if (parts[0] == "era" && parts.size()>1) {
336  res = generate_era_topics(sort_generated, parts[1]);
337  } else {
338  WRN_HP << "Found a topic generator that I didn't recognize: " << generator << "\n";
339  }
340  }
341 
342  return res;
343 }
344 
345 void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
346 {
347  if (generator == "races") {
348  generate_races_sections(help_cfg, sec, level);
349  } else if (generator == "terrains") {
350  generate_terrain_sections(sec, level);
351  } else if (generator == "eras") {
352  DBG_HP << "Generating eras...\n";
353  generate_era_sections(help_cfg, sec, level);
354  } else {
355  std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
356  if (parts.size() > 1 && parts[0] == "units") {
357  generate_unit_sections(help_cfg, sec, level, true, parts[1]);
358  } else if (generator.size() > 0) {
359  WRN_HP << "Found a section generator that I didn't recognize: " << generator << "\n";
360  }
361  }
362 }
363 
364 std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec, const std::vector<topic>& generated_topics)
365 {
366  std::string empty_string = "";
367  if (generator.empty()) {
368  return empty_string;
369  } else {
370  std::vector<std::string> parts = utils::split(generator, ':');
371  if (parts.size() > 1 && parts[0] == "contents") {
372  if (parts[1] == "generated") {
373  return generate_contents_links(sec, generated_topics);
374  } else {
375  return generate_contents_links(parts[1], help_cfg);
376  }
377  }
378  }
379  return empty_string;
380 }
381 
382 topic_text& topic_text::operator=(std::shared_ptr<topic_generator> g)
383 {
384  generator_ = g;
385  return *this;
386 }
387 
388 const std::vector<std::string>& topic_text::parsed_text() const
389 {
390  if (generator_) {
392  // This caches the result, so doesn't need the generator any more
393  generator_.reset();
394  }
395  return parsed_text_;
396 }
397 
398 static std::string time_of_day_bonus_colored(const int time_of_day_bonus)
399 {
400  return std::string("<format>color='") + (time_of_day_bonus > 0 ? "green" : (time_of_day_bonus < 0 ? "red" : "white")) + "' text='" + std::to_string(time_of_day_bonus) + "'</format>";
401 }
402 
403 std::vector<topic> generate_time_of_day_topics(const bool /*sort_generated*/)
404 {
405  std::vector<topic> topics;
406  std::stringstream toplevel;
407 
408  if (! resources::tod_manager) {
409  toplevel << _("Only available during a scenario.");
410  topics.emplace_back(_("Time of Day Schedule"), "..schedule", toplevel.str());
411  return topics;
412  }
413 
414  const std::vector<time_of_day>& times = resources::tod_manager->times();
415  for (const time_of_day& time : times)
416  {
417  const std::string id = "time_of_day_" + time.id;
418  const std::string image = "<img>src='" + time.image + "'</img>";
419  const std::string image_lawful = "<img>src='icons/alignments/alignment_lawful_30.png'</img>";
420  const std::string image_neutral = "<img>src='icons/alignments/alignment_neutral_30.png'</img>";
421  const std::string image_chaotic = "<img>src='icons/alignments/alignment_chaotic_30.png'</img>";
422  const std::string image_liminal = "<img>src='icons/alignments/alignment_liminal_30.png'</img>";
423  std::stringstream text;
424 
425  const int lawful_bonus = generic_combat_modifier(time.lawful_bonus, unit_type::ALIGNMENT::LAWFUL, false, resources::tod_manager->get_max_liminal_bonus());
426  const int neutral_bonus = generic_combat_modifier(time.lawful_bonus, unit_type::ALIGNMENT::NEUTRAL, false, resources::tod_manager->get_max_liminal_bonus());
427  const int chaotic_bonus = generic_combat_modifier(time.lawful_bonus, unit_type::ALIGNMENT::CHAOTIC, false, resources::tod_manager->get_max_liminal_bonus());
428  const int liminal_bonus = generic_combat_modifier(time.lawful_bonus, unit_type::ALIGNMENT::LIMINAL, false, resources::tod_manager->get_max_liminal_bonus());
429 
430  toplevel << make_link(time.name.str(), id) << jump_to(160) << image << jump(30) <<
431  image_lawful << time_of_day_bonus_colored(lawful_bonus) << jump_to(390) <<
432  image_neutral << time_of_day_bonus_colored(neutral_bonus) << jump_to(450) <<
433  image_chaotic << time_of_day_bonus_colored(chaotic_bonus) << jump_to(520) <<
434  image_liminal << time_of_day_bonus_colored(liminal_bonus) << '\n';
435 
436  text << image << '\n' << time.description.str() << '\n' <<
437  image_lawful << _("Lawful Bonus:") << ' ' << time_of_day_bonus_colored(lawful_bonus) << '\n' <<
438  image_neutral << _("Neutral Bonus:") << ' ' << time_of_day_bonus_colored(neutral_bonus) << '\n' <<
439  image_chaotic << _("Chaotic Bonus:") << ' ' << time_of_day_bonus_colored(chaotic_bonus) << '\n' <<
440  image_liminal << _("Liminal Bonus:") << ' ' << time_of_day_bonus_colored(liminal_bonus) << '\n' <<
441  '\n' << make_link(_("Schedule"), "..schedule");
442 
443  topics.emplace_back(time.name.str(), id, text.str());
444  }
445 
446  topics.emplace_back(_("Time of Day Schedule"), "..schedule", toplevel.str());
447  return topics;
448 }
449 
450 std::vector<topic> generate_weapon_special_topics(const bool sort_generated)
451 {
452  std::vector<topic> topics;
453 
454  std::map<t_string, std::string> special_description;
455  std::map<t_string, std::set<std::string, string_less>> special_units;
456 
457  for (const unit_type_data::unit_type_map::value_type &type_mapping : unit_types.types())
458  {
459  const unit_type &type = type_mapping.second;
460  // Only show the weapon special if we find it on a unit that
461  // detailed description should be shown about.
462  if (description_type(type) != FULL_DESCRIPTION)
463  continue;
464 
465  for (const attack_type& atk : type.attacks()) {
466 
467  std::vector<std::pair<t_string, t_string>> specials = atk.special_tooltips();
468  for ( std::size_t i = 0; i != specials.size(); ++i )
469  {
470  special_description.emplace(specials[i].first, specials[i].second);
471 
472  if (!type.hide_help()) {
473  //add a link in the list of units having this special
474  std::string type_name = type.type_name();
475  //check for variations (walking corpse/soulless etc)
476  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
477  std::string ref_id = section_prefix + unit_prefix + type.id();
478  //we put the translated name at the beginning of the hyperlink,
479  //so the automatic alphabetic sorting of std::set can use it
480  std::string link = make_link(type_name, ref_id);
481  special_units[specials[i].first].insert(link);
482  }
483  }
484  }
485 
486  for(config adv : type.modification_advancements()) {
487  for(config effect : adv.child_range("effect")) {
488  if(effect["apply_to"] == "new_attack" && effect.has_child("specials")) {
489  for(config::any_child spec : effect.child("specials").all_children_range()) {
490  if(!spec.cfg["name"].empty()) {
491  special_description.emplace(spec.cfg["name"].t_str(), spec.cfg["description"].t_str());
492  if(!type.hide_help()) {
493  //add a link in the list of units having this special
494  std::string type_name = type.type_name();
495  //check for variations (walking corpse/soulless etc)
496  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
497  std::string ref_id = section_prefix + unit_prefix + type.id();
498  //we put the translated name at the beginning of the hyperlink,
499  //so the automatic alphabetic sorting of std::set can use it
500  std::string link = make_link(type_name, ref_id);
501  special_units[spec.cfg["name"]].insert(link);
502  }
503  }
504  }
505  } else if(effect["apply_to"] == "attack" && effect.has_child("set_specials")) {
506  for(config::any_child spec : effect.child("set_specials").all_children_range()) {
507  if(!spec.cfg["name"].empty()) {
508  special_description.emplace(spec.cfg["name"].t_str(), spec.cfg["description"].t_str());
509  if(!type.hide_help()) {
510  //add a link in the list of units having this special
511  std::string type_name = type.type_name();
512  //check for variations (walking corpse/soulless etc)
513  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
514  std::string ref_id = section_prefix + unit_prefix + type.id();
515  //we put the translated name at the beginning of the hyperlink,
516  //so the automatic alphabetic sorting of std::set can use it
517  std::string link = make_link(type_name, ref_id);
518  special_units[spec.cfg["name"]].insert(link);
519  }
520  }
521  }
522  }
523  }
524  }
525  }
526 
527  for (std::map<t_string, std::string>::iterator s = special_description.begin();
528  s != special_description.end(); ++s) {
529  // use untranslated name to have universal topic id
530  std::string id = "weaponspecial_" + s->first.base_str();
531  std::stringstream text;
532  text << s->second;
533  text << "\n\n" << _("<header>text='Units with this special attack'</header>") << "\n";
534  std::set<std::string, string_less> &units = special_units[s->first];
535  for (std::set<std::string, string_less>::iterator u = units.begin(); u != units.end(); ++u) {
536  text << font::unicode_bullet << " " << (*u) << "\n";
537  }
538 
539  topics.emplace_back(s->first, id, text.str());
540  }
541 
542  if (sort_generated)
543  std::sort(topics.begin(), topics.end(), title_less());
544  return topics;
545 }
546 
547 std::vector<topic> generate_ability_topics(const bool sort_generated)
548 {
549  std::vector<topic> topics;
550 
551  std::map<std::string, const unit_type::ability_metadata*> ability_topic_data;
552  std::map<std::string, std::set<std::string, string_less>> ability_units;
553 
554  const auto parse = [&](const unit_type& type, const unit_type::ability_metadata& ability) {
555  // NOTE: neither ability names nor ability ids are necessarily unique. Creating
556  // topics for either each unique name or each unique id means certain abilities
557  // will be excluded from help. So... the ability topic ref id is a combination
558  // of id and (untranslated) name. It's rather ugly, but it works.
559  const std::string topic_ref = ability.id + ability.name.base_str();
560 
561  ability_topic_data.emplace(topic_ref, &ability);
562 
563  if(!type.hide_help()) {
564  // Add a link in the list of units with this ability
565  // We put the translated name at the beginning of the hyperlink,
566  // so the automatic alphabetic sorting of std::set can use it
567  const std::string link = make_link(type.type_name(), unit_prefix + type.id());
568  ability_units[topic_ref].insert(link);
569  }
570  };
571 
572  // Look through all the unit types. If a unit of that type would have a full
573  // description, add its abilities to the potential topic list. We don't want
574  // to show abilities that the user has not encountered yet.
575  for(const auto& type_mapping : unit_types.types()) {
576  const unit_type& type = type_mapping.second;
577 
578  if(description_type(type) != FULL_DESCRIPTION) {
579  continue;
580  }
581 
582  for(const unit_type::ability_metadata& ability : type.abilities_metadata()) {
583  parse(type, ability);
584  }
585 
586  for(const unit_type::ability_metadata& ability : type.adv_abilities_metadata()) {
587  parse(type, ability);
588  }
589  }
590 
591  for(const auto& a : ability_topic_data) {
592  if (a.second->name.empty()) {
593  continue;
594  }
595  std::ostringstream text;
596  text << a.second->description;
597  text << "\n\n" << _("<header>text='Units with this ability'</header>") << "\n";
598 
599  for(const auto& u : ability_units[a.first]) { // first is the topic ref id
600  text << font::unicode_bullet << " " << u << "\n";
601  }
602 
603  topics.emplace_back(a.second->name, ability_prefix + a.first, text.str());
604  }
605 
606  if(sort_generated) {
607  std::sort(topics.begin(), topics.end(), title_less());
608  }
609 
610  return topics;
611 }
612 
613 std::vector<topic> generate_era_topics(const bool sort_generated, const std::string & era_id)
614 {
615  std::vector<topic> topics;
616 
617  const config & era = game_cfg->find_child("era","id", era_id);
618  if(era && !era["hide_help"].to_bool()) {
619  topics = generate_faction_topics(era, sort_generated);
620 
621  std::vector<std::string> faction_links;
622  for (const topic & t : topics) {
623  faction_links.push_back(make_link(t.title, t.id));
624  }
625 
626  std::stringstream text;
627  text << "<header>text='" << _("Era:") << " " << era["name"] << "'</header>" << "\n";
628  text << "\n";
629  const config::attribute_value& description = era["description"];
630  if (!description.empty()) {
631  text << description.t_str() << "\n";
632  text << "\n";
633  }
634 
635  text << "<header>text='" << _("Factions") << "'</header>" << "\n";
636 
637  std::sort(faction_links.begin(), faction_links.end());
638  for (const std::string &link : faction_links) {
639  text << font::unicode_bullet << " " << link << "\n";
640  }
641 
642  topic era_topic(era["name"], ".." + era_prefix + era["id"].str(), text.str());
643 
644  topics.push_back( era_topic );
645  }
646  return topics;
647 }
648 
649 std::vector<topic> generate_faction_topics(const config & era, const bool sort_generated)
650 {
651  std::vector<topic> topics;
652  for (const config &f : era.child_range("multiplayer_side")) {
653  const std::string& id = f["id"];
654  if (id == "Random")
655  continue;
656 
657  std::stringstream text;
658 
659  const config::attribute_value& description = f["description"];
660  if (!description.empty()) {
661  text << description.t_str() << "\n";
662  text << "\n";
663  }
664 
665  const std::vector<std::string> recruit_ids = utils::split(f["recruit"]);
666  std::set<std::string> races;
667  std::set<std::string> alignments;
668 
669  for (const std::string & u_id : recruit_ids) {
670  if (const unit_type * t = unit_types.find(u_id, unit_type::HELP_INDEXED)) {
671  assert(t);
672  const unit_type & type = *t;
673 
674  if (const unit_race *r = unit_types.find_race(type.race_id())) {
675  races.insert(make_link(r->plural_name(), std::string("..") + race_prefix + r->id()));
676  }
677  alignments.insert(make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
678  }
679  }
680 
681  if (!races.empty()) {
682  std::set<std::string>::iterator it = races.begin();
683  text << _("Races: ") << *(it++);
684  while(it != races.end()) {
685  text << ", " << *(it++);
686  }
687  text << "\n\n";
688  }
689 
690  if (!alignments.empty()) {
691  std::set<std::string>::iterator it = alignments.begin();
692  text << _("Alignments: ") << *(it++);
693  while(it != alignments.end()) {
694  text << ", " << *(it++);
695  }
696  text << "\n\n";
697  }
698 
699  text << "<header>text='" << _("Leaders") << "'</header>" << "\n";
700  const std::vector<std::string> leaders =
701  make_unit_links_list( utils::split(f["leader"]), true );
702  for (const std::string &link : leaders) {
703  text << font::unicode_bullet << " " << link << "\n";
704  }
705 
706  text << "\n";
707 
708  text << "<header>text='" << _("Recruits") << "'</header>" << "\n";
709  const std::vector<std::string> recruit_links =
710  make_unit_links_list( recruit_ids, true );
711  for (const std::string &link : recruit_links) {
712  text << font::unicode_bullet << " " << link << "\n";
713  }
714 
715  const std::string name = f["name"];
716  const std::string ref_id = faction_prefix + era["id"] + "_" + id;
717  topics.emplace_back(name, ref_id, text.str());
718  }
719  if (sort_generated)
720  std::sort(topics.begin(), topics.end(), title_less());
721  return topics;
722 }
723 
724 std::vector<topic> generate_trait_topics(const bool sort_generated)
725 {
726  std::vector<topic> topics;
727  std::map<t_string, const config> trait_list;
728 
729  for (const config & trait : unit_types.traits()) {
730  const std::string trait_id = trait["id"];
731  trait_list.emplace(trait_id, trait);
732  }
733 
734 
735  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
736  {
737  const unit_type &type = i.second;
738  const auto desc_type = description_type(type);
739  if (desc_type == FULL_DESCRIPTION || desc_type == HIDDEN_BUT_SHOW_MACROS) {
740  if (config::const_child_itors traits = type.possible_traits()) {
741  for (const config & trait : traits) {
742  const std::string trait_id = trait["id"];
743  trait_list.emplace(trait_id, trait);
744  }
745  }
746  if (const unit_race *r = unit_types.find_race(type.race_id())) {
747  for (const config & trait : r->additional_traits()) {
748  const std::string trait_id = trait["id"];
749  trait_list.emplace(trait_id, trait);
750  }
751  }
752  }
753  }
754 
755  for (std::map<t_string, const config>::iterator a = trait_list.begin(); a != trait_list.end(); ++a) {
756  std::string id = "traits_" + a->first;
757  const config trait = a->second;
758 
759  std::string name = trait["male_name"].str();
760  if (name.empty()) name = trait["female_name"].str();
761  if (name.empty()) name = trait["name"].str();
762  if (name.empty()) continue; // Hidden trait
763 
764  std::stringstream text;
765  if (!trait["help_text"].empty()) {
766  text << trait["help_text"];
767  } else if (!trait["description"].empty()) {
768  text << trait["description"];
769  } else {
770  text << _("No description available.");
771  }
772  text << "\n\n";
773  if (trait["availability"] == "musthave") {
774  text << _("Availability: ") << _("Must-have") << "\n";
775  } else if (trait["availability"] == "none") {
776  text << _("Availability: ") << _("Unavailable") << "\n";
777  }
778  topics.emplace_back(name, id, text.str());
779  }
780 
781  if (sort_generated)
782  std::sort(topics.begin(), topics.end(), title_less());
783  return topics;
784 }
785 
786 
787 std::string make_unit_link(const std::string& type_id)
788 {
789  std::string link;
790 
792  if (!type) {
793  std::cerr << "Unknown unit type : " << type_id << "\n";
794  // don't return an hyperlink (no page)
795  // instead show the id (as hint)
796  link = type_id;
797  } else if (!type->hide_help()) {
798  std::string name = type->type_name();
799  std::string ref_id;
800  if (description_type(*type) == FULL_DESCRIPTION) {
801  const std::string section_prefix = type->show_variations_in_help() ? ".." : "";
802  ref_id = section_prefix + unit_prefix + type->id();
803  } else {
804  ref_id = unknown_unit_topic;
805  name += " (?)";
806  }
807  link = make_link(name, ref_id);
808  } // if hide_help then link is an empty string
809 
810  return link;
811 }
812 
813 std::vector<std::string> make_unit_links_list(const std::vector<std::string>& type_id_list, bool ordered)
814 {
815  std::vector<std::string> links_list;
816  for (const std::string &type_id : type_id_list) {
817  std::string unit_link = make_unit_link(type_id);
818  if (!unit_link.empty())
819  links_list.push_back(unit_link);
820  }
821 
822  if (ordered)
823  std::sort(links_list.begin(), links_list.end());
824 
825  return links_list;
826 }
827 
828 void generate_races_sections(const config *help_cfg, section &sec, int level)
829 {
830  std::set<std::string, string_less> races;
831  std::set<std::string, string_less> visible_races;
832 
833  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
834  {
835  const unit_type &type = i.second;
836  UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
837  if (desc_type == FULL_DESCRIPTION) {
838  races.insert(type.race_id());
839  if (!type.hide_help())
840  visible_races.insert(type.race_id());
841  }
842  }
843 
844  for(std::set<std::string, string_less>::iterator it = races.begin(); it != races.end(); ++it) {
845  section race_section;
846  config section_cfg;
847 
848  bool hidden = (visible_races.count(*it) == 0);
849 
850  section_cfg["id"] = hidden_symbol(hidden) + race_prefix + *it;
851 
852  std::string title;
853  if (const unit_race *r = unit_types.find_race(*it)) {
854  title = r->plural_name();
855  } else {
856  title = _ ("race^Miscellaneous");
857  }
858  section_cfg["title"] = title;
859 
860  section_cfg["sections_generator"] = "units:" + *it;
861  section_cfg["generator"] = "units:" + *it;
862 
863  parse_config_internal(help_cfg, &section_cfg, race_section, level+1);
864  sec.add_section(race_section);
865  }
866 }
867 
868 void generate_era_sections(const config* help_cfg, section & sec, int level)
869 {
870  for (const config & era : game_cfg->child_range("era")) {
871  if (era["hide_help"].to_bool()) {
872  continue;
873  }
874 
875  DBG_HP << "Adding help section: " << era["id"].str() << "\n";
876 
877  section era_section;
878  config section_cfg;
879  section_cfg["id"] = era_prefix + era["id"].str();
880  section_cfg["title"] = era["name"];
881 
882  section_cfg["generator"] = "era:" + era["id"].str();
883 
884  DBG_HP << section_cfg.debug() << "\n";
885 
886  parse_config_internal(help_cfg, &section_cfg, era_section, level+1);
887  sec.add_section(era_section);
888  }
889 }
890 
891 void generate_terrain_sections(section& sec, int /*level*/)
892 {
893  std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data();
894 
895  if (!tdata) {
896  WRN_HP << "When building terrain help sections, couldn't acquire terrain types data, aborting.\n";
897  return;
898  }
899 
900  std::map<std::string, section> base_map;
901 
902  const t_translation::ter_list& t_listi = tdata->list();
903 
904  for (const t_translation::terrain_code& t : t_listi) {
905 
906  const terrain_type& info = tdata->get_terrain_info(t);
907 
908  bool hidden = info.hide_help();
909 
911  == preferences::encountered_terrains().end() && !info.is_overlay())
912  hidden = true;
913 
914  topic terrain_topic;
915  terrain_topic.title = info.editor_name();
916  terrain_topic.id = hidden_symbol(hidden) + terrain_prefix + info.id();
917  terrain_topic.text = std::make_shared<terrain_topic_generator>(info);
918 
919  t_translation::ter_list base_terrains = tdata->underlying_union_terrain(t);
920  for (const t_translation::terrain_code& base : base_terrains) {
921 
922  const terrain_type& base_info = tdata->get_terrain_info(base);
923 
924  if (!base_info.is_nonnull() || base_info.hide_help())
925  continue;
926 
927  section& base_section = base_map[base_info.id()];
928 
929  base_section.id = terrain_prefix + base_info.id();
930  base_section.title = base_info.editor_name();
931 
932  if (base_info.id() == info.id())
933  terrain_topic.id = ".." + terrain_prefix + info.id();
934  base_section.topics.push_back(terrain_topic);
935  }
936  }
937 
938  for (const auto& base : base_map) {
939  sec.add_section(base.second);
940  }
941 }
942 
943 void generate_unit_sections(const config* /*help_cfg*/, section& sec, int /*level*/, const bool /*sort_generated*/, const std::string& race)
944 {
945  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types()) {
946  const unit_type &type = i.second;
947 
948  if (type.race_id() != race)
949  continue;
950 
951  if (!type.show_variations_in_help())
952  continue;
953 
954  section base_unit;
955  for (const std::string &variation_id : type.variations()) {
956  // TODO: Do we apply encountered stuff to variations?
957  const unit_type &var_type = type.get_variation(variation_id);
958  const std::string topic_name = var_type.type_name() + "\n" + var_type.variation_name();
959  const std::string var_ref = hidden_symbol(var_type.hide_help()) + variation_prefix + var_type.id() + "_" + variation_id;
960 
961  topic var_topic(topic_name, var_ref, "");
962  var_topic.text = std::make_shared<unit_topic_generator>(var_type, variation_id);
963  base_unit.topics.push_back(var_topic);
964  }
965 
966  const std::string type_name = type.type_name();
967  const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix + type.id();
968 
969  base_unit.id = ref_id;
970  base_unit.title = type_name;
971 
972  sec.add_section(base_unit);
973  }
974 }
975 
976 std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
977 {
978  std::vector<topic> topics;
979  std::set<std::string, string_less> race_units;
980  std::set<std::string, string_less> race_topics;
981  std::set<std::string> alignments;
982 
983  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
984  {
985  const unit_type &type = i.second;
986 
987  if (type.race_id() != race)
988  continue;
989 
990  UNIT_DESCRIPTION_TYPE desc_type = description_type(type);
991  if (desc_type != FULL_DESCRIPTION)
992  continue;
993 
994  const std::string debug_suffix = (game_config::debug ? " (" + type.id() + ")" : "");
995  const std::string type_name = type.type_name() + (type.id() == type.type_name().str() ? "" : debug_suffix);
996  const std::string real_prefix = type.show_variations_in_help() ? ".." : "";
997  const std::string ref_id = hidden_symbol(type.hide_help()) + real_prefix + unit_prefix + type.id();
998  topic unit_topic(type_name, ref_id, "");
999  unit_topic.text = std::make_shared<unit_topic_generator>(type);
1000  topics.push_back(unit_topic);
1001 
1002  if (!type.hide_help()) {
1003  // we also record an hyperlink of this unit
1004  // in the list used for the race topic
1005  std::string link = make_link(type_name, ref_id);
1006  race_units.insert(link);
1007 
1008  alignments.insert(make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
1009  }
1010  }
1011 
1012  //generate the hidden race description topic
1013  std::string race_id = "..race_"+race;
1014  std::string race_name;
1015  std::string race_description;
1016  std::string race_help_taxonomy;
1017  if (const unit_race *r = unit_types.find_race(race)) {
1018  race_name = r->plural_name();
1019  race_description = r->description();
1020  race_help_taxonomy = r->help_taxonomy();
1021  // if (description.empty()) description = _("No description Available");
1022  for (const config &additional_topic : r->additional_topics())
1023  {
1024  std::string id = additional_topic["id"];
1025  std::string title = additional_topic["title"];
1026  std::string text = additional_topic["text"];
1027  //topic additional_topic(title, id, text);
1028  topics.emplace_back(title,id,text);
1029  std::string link = make_link(title, id);
1030  race_topics.insert(link);
1031  }
1032  } else {
1033  race_name = _ ("race^Miscellaneous");
1034  // description = _("Here put the description of the Miscellaneous race");
1035  }
1036 
1037  // Find any other races whose [race]help_taxonomy points to the current race
1038  std::map<std::string, t_string> subgroups;
1039  for (const auto &r : unit_types.races()) {
1040  if (r.second.help_taxonomy() == race) {
1041  if (!r.second.plural_name().empty())
1042  subgroups[r.first] = r.second.plural_name();
1043  else
1044  subgroups[r.first] = r.first;
1045  }
1046  }
1047 
1048  std::stringstream text;
1049 
1050  if (!race_description.empty()) {
1051  text << race_description << "\n\n";
1052  }
1053 
1054  if (!alignments.empty()) {
1055  std::set<std::string>::iterator it = alignments.begin();
1056  text << (alignments.size() > 1 ? _("Alignments: ") : _("Alignment: ")) << *(it++);
1057  while(it != alignments.end()) {
1058  text << ", " << *(it++);
1059  }
1060  text << "\n\n";
1061  }
1062 
1063  if (!race_help_taxonomy.empty()) {
1064  utils::string_map symbols;
1065  symbols["topic_id"] = "..race_"+race_help_taxonomy;
1066  if (const unit_race *r = unit_types.find_race(race_help_taxonomy)) {
1067  symbols["help_taxonomy"] = r->plural_name();
1068  } else {
1069  // Fall back to using showing the race id for the race that we couldn't find.
1070  // Not great, but probably useful if UMC has a broken link.
1071  symbols["help_taxonomy"] = race_help_taxonomy;
1072  }
1073  // TRANSLATORS: this is expected to say "[Dunefolk are] a group of units, all of whom are Humans",
1074  // or "[Quenoth Elves are] a group of units, all of whom are Elves".
1075  text << VGETTEXT("This is a group of units, all of whom are <ref>dst='$topic_id' text='$help_taxonomy'</ref>", symbols) << "\n\n";
1076  }
1077 
1078  if (!subgroups.empty()) {
1079  if (!race_help_taxonomy.empty()) {
1080  text << _("<header>text='Subgroups of units within this group'</header>") << "\n";
1081  } else {
1082  text << _("<header>text='Groups of units within this race'</header>") << "\n";
1083  }
1084  for (const auto &sg : subgroups) {
1085  text << font::unicode_bullet << " " << make_link(sg.second, "..race_" + sg.first) << "\n";
1086  }
1087  text << "\n";
1088  }
1089 
1090  if (!race_help_taxonomy.empty()) {
1091  text << _("<header>text='Units of this group'</header>") << "\n";
1092  } else {
1093  text << _("<header>text='Units of this race'</header>") << "\n";
1094  }
1095  for (const auto &u : race_units) {
1096  text << font::unicode_bullet << " " << u << "\n";
1097  }
1098 
1099  topics.emplace_back(race_name, race_id, text.str());
1100 
1101  if (sort_generated)
1102  std::sort(topics.begin(), topics.end(), title_less());
1103 
1104  return topics;
1105 }
1106 
1108 {
1111  return FULL_DESCRIPTION;
1112  }
1113 
1114  const std::set<std::string> &encountered_units = preferences::encountered_units();
1115  if (encountered_units.find(type.id()) != encountered_units.end()) {
1116  return FULL_DESCRIPTION;
1117  }
1118 
1119  // See the docs of HIDDEN_BUT_SHOW_MACROS
1120  if (type.id() == "Fog Clearer") {
1121  return HIDDEN_BUT_SHOW_MACROS;
1122  }
1123 
1124  return NO_DESCRIPTION;
1125 }
1126 
1127 std::string generate_contents_links(const std::string& section_name, config const *help_cfg)
1128 {
1129  const config& section_cfg = help_cfg->find_child("section", "id", section_name);
1130  if (!section_cfg) {
1131  return std::string();
1132  }
1133 
1134  std::ostringstream res;
1135 
1136  std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);
1137 
1138  // we use an intermediate structure to allow a conditional sorting
1139  typedef std::pair<std::string,std::string> link;
1140  std::vector<link> topics_links;
1141 
1143  // Find all topics in this section.
1144  for (t = topics.begin(); t != topics.end(); ++t) {
1145  if (const config& topic_cfg = help_cfg->find_child("topic", "id", *t)) {
1146  std::string id = topic_cfg["id"];
1147  if (is_visible_id(id))
1148  topics_links.emplace_back(topic_cfg["title"], id);
1149  }
1150  }
1151 
1152  if (section_cfg["sort_topics"] == "yes") {
1153  std::sort(topics_links.begin(),topics_links.end());
1154  }
1155 
1157  for (l = topics_links.begin(); l != topics_links.end(); ++l) {
1158  std::string link = make_link(l->first, l->second);
1159  res << font::unicode_bullet << " " << link << "\n";
1160  }
1161 
1162  return res.str();
1163 }
1164 
1165 std::string generate_contents_links(const section &sec, const std::vector<topic>& topics)
1166 {
1167  std::stringstream res;
1168 
1169  for (auto &s : sec.sections) {
1170  if (is_visible_id(s.id)) {
1171  std::string link = make_link(s.title, ".."+s.id);
1172  res << font::unicode_bullet << " " << link << "\n";
1173  }
1174  }
1175 
1176  for (auto &t : topics) {
1177  if (is_visible_id(t.id)) {
1178  std::string link = make_link(t.title, t.id);
1179  res << font::unicode_bullet << " " << link << "\n";
1180  }
1181  }
1182  return res.str();
1183 }
1184 
1185 bool topic::operator==(const topic &t) const
1186 {
1187  return t.id == id;
1188 }
1189 
1190 bool topic::operator<(const topic &t) const
1191 {
1192  return id < t.id;
1193 }
1194 
1195 bool section::operator==(const section &sec) const
1196 {
1197  return sec.id == id;
1198 }
1199 
1200 bool section::operator<(const section &sec) const
1201 {
1202  return id < sec.id;
1203 }
1204 
1206 {
1207  sections.emplace_back(s);
1208 }
1209 
1211 {
1212  topics.clear();
1213  sections.clear();
1214 }
1215 
1216 const topic *find_topic(const section &sec, const std::string &id)
1217 {
1218  topic_list::const_iterator tit =
1219  std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
1220  if (tit != sec.topics.end()) {
1221  return &(*tit);
1222  }
1223  for (const auto &s : sec.sections) {
1224  const auto t = find_topic(s, id);
1225  if (t != nullptr) {
1226  return t;
1227  }
1228  }
1229  return nullptr;
1230 }
1231 
1232 const section *find_section(const section &sec, const std::string &id)
1233 {
1234  const auto &sit =
1235  std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
1236  if (sit != sec.sections.end()) {
1237  return &*sit;
1238  }
1239  for (const auto &subsection : sec.sections) {
1240  const auto s = find_section(subsection, id);
1241  if (s != nullptr) {
1242  return s;
1243  }
1244  }
1245  return nullptr;
1246 }
1247 
1248 section *find_section(section &sec, const std::string &id)
1249 {
1250  return const_cast<section *>(find_section(const_cast<const section &>(sec), id));
1251 }
1252 
1253 std::vector<std::string> parse_text(const std::string &text)
1254 {
1255  std::vector<std::string> res;
1256  bool last_char_escape = false;
1257  const char escape_char = '\\';
1258  std::stringstream ss;
1259  std::size_t pos;
1260  enum { ELEMENT_NAME, OTHER } state = OTHER;
1261  for (pos = 0; pos < text.size(); ++pos) {
1262  const char c = text[pos];
1263  if (c == escape_char && !last_char_escape) {
1264  last_char_escape = true;
1265  }
1266  else {
1267  if (state == OTHER) {
1268  if (c == '<') {
1269  if (last_char_escape) {
1270  ss << c;
1271  }
1272  else {
1273  res.push_back(ss.str());
1274  ss.str("");
1275  state = ELEMENT_NAME;
1276  }
1277  }
1278  else {
1279  ss << c;
1280  }
1281  }
1282  else if (state == ELEMENT_NAME) {
1283  if (c == '/') {
1284  std::string msg = "Erroneous / in element name.";
1285  throw parse_error(msg);
1286  }
1287  else if (c == '>') {
1288  // End of this name.
1289  std::stringstream s;
1290  const std::string element_name = ss.str();
1291  ss.str("");
1292  s << "</" << element_name << ">";
1293  const std::string end_element_name = s.str();
1294  std::size_t end_pos = text.find(end_element_name, pos);
1295  if (end_pos == std::string::npos) {
1296  std::stringstream msg;
1297  msg << "Unterminated element: " << element_name;
1298  throw parse_error(msg.str());
1299  }
1300  s.str("");
1301  const std::string contents = text.substr(pos + 1, end_pos - pos - 1);
1302  const std::string element = convert_to_wml(element_name, contents);
1303  res.push_back(element);
1304  pos = end_pos + end_element_name.size() - 1;
1305  state = OTHER;
1306  }
1307  else {
1308  ss << c;
1309  }
1310  }
1311  last_char_escape = false;
1312  }
1313  }
1314  if (state == ELEMENT_NAME) {
1315  std::stringstream msg;
1316  msg << "Element '" << ss.str() << "' continues through end of string.";
1317  throw parse_error(msg.str());
1318  }
1319  if (!ss.str().empty()) {
1320  // Add the last string.
1321  res.push_back(ss.str());
1322  }
1323  return res;
1324 }
1325 
1326 std::string convert_to_wml(const std::string &element_name, const std::string &contents)
1327 {
1328  std::stringstream ss;
1329  bool in_quotes = false;
1330  bool last_char_escape = false;
1331  const char escape_char = '\\';
1332  std::vector<std::string> attributes;
1333  // Find the different attributes.
1334  // No checks are made for the equal sign or something like that.
1335  // Attributes are just separated by spaces or newlines.
1336  // Attributes that contain spaces must be in single quotes.
1337  for (std::size_t pos = 0; pos < contents.size(); ++pos) {
1338  const char c = contents[pos];
1339  if (c == escape_char && !last_char_escape) {
1340  last_char_escape = true;
1341  }
1342  else {
1343  if (c == '\'' && !last_char_escape) {
1344  ss << '"';
1345  in_quotes = !in_quotes;
1346  }
1347  else if ((c == ' ' || c == '\n') && !last_char_escape && !in_quotes) {
1348  // Space or newline, end of attribute.
1349  attributes.push_back(ss.str());
1350  ss.str("");
1351  }
1352  else {
1353  ss << c;
1354  }
1355  last_char_escape = false;
1356  }
1357  }
1358  if (in_quotes) {
1359  std::stringstream msg;
1360  msg << "Unterminated single quote after: '" << ss.str() << "'";
1361  throw parse_error(msg.str());
1362  }
1363  if (!ss.str().empty()) {
1364  attributes.push_back(ss.str());
1365  }
1366  ss.str("");
1367  // Create the WML.
1368  ss << "[" << element_name << "]\n";
1369  for (std::vector<std::string>::const_iterator it = attributes.begin();
1370  it != attributes.end(); ++it) {
1371  ss << *it << "\n";
1372  }
1373  ss << "[/" << element_name << "]\n";
1374  return ss.str();
1375 }
1376 
1377 color_t string_to_color(const std::string &cmp_str)
1378 {
1379  if (cmp_str == "green") {
1380  return font::GOOD_COLOR;
1381  }
1382  if (cmp_str == "red") {
1383  return font::BAD_COLOR;
1384  }
1385  if (cmp_str == "black") {
1386  return font::BLACK_COLOR;
1387  }
1388  if (cmp_str == "yellow") {
1389  return font::YELLOW_COLOR;
1390  }
1391  if (cmp_str == "white") {
1392  return font::BIGMAP_COLOR;
1393  }
1394  // a #rrggbb color in pango format.
1395  if (*cmp_str.c_str() == '#' && cmp_str.size() == 7) {
1396  return color_t::from_hex_string(cmp_str.substr(1));
1397  }
1398  return font::NORMAL_COLOR;
1399 }
1400 
1401 std::vector<std::string> split_in_width(const std::string &s, const int font_size,
1402  const unsigned width)
1403 {
1404  std::vector<std::string> res;
1405  try {
1406  const std::string& first_line = font::pango_word_wrap(s, font_size, width, -1, 1, true);
1407  res.push_back(first_line);
1408  if(s.size() > first_line.size()) {
1409  res.push_back(s.substr(first_line.size()));
1410  }
1411  }
1413  {
1414  throw parse_error (_("corrupted original file"));
1415  }
1416 
1417  return res;
1418 }
1419 
1420 std::string remove_first_space(const std::string& text)
1421 {
1422  if (text.length() > 0 && text[0] == ' ') {
1423  return text.substr(1);
1424  }
1425  return text;
1426 }
1427 
1428 std::string get_first_word(const std::string &s)
1429 {
1430  std::size_t first_word_start = s.find_first_not_of(' ');
1431  if (first_word_start == std::string::npos) {
1432  return s;
1433  }
1434  std::size_t first_word_end = s.find_first_of(" \n", first_word_start);
1435  if( first_word_end == first_word_start ) {
1436  // This word is '\n'.
1437  first_word_end = first_word_start+1;
1438  }
1439 
1440  //if no gap(' ' or '\n') found, test if it is CJK character
1441  std::string re = s.substr(0, first_word_end);
1442 
1443  utf8::iterator ch(re);
1444  if (ch == utf8::iterator::end(re))
1445  return re;
1446 
1447  char32_t firstchar = *ch;
1448  if (is_cjk_char(firstchar)) {
1449  re = unicode_cast<std::string>(firstchar);
1450  }
1451  return re;
1452 }
1453 
1455 {
1456  default_toplevel.clear();
1457  hidden_sections.clear();
1458  if (game_cfg != nullptr) {
1459  const config *help_config = &game_cfg->child_or_empty("help");
1460  try {
1461  default_toplevel = parse_config(help_config);
1462  // Create a config object that contains everything that is
1463  // not referenced from the toplevel element. Read this
1464  // config and save these sections and topics so that they
1465  // can be referenced later on when showing help about
1466  // specified things, but that should not be shown when
1467  // opening the help browser in the default manner.
1468  config hidden_toplevel;
1469  std::stringstream ss;
1470  for (const config &section : help_config->child_range("section"))
1471  {
1472  const std::string id = section["id"];
1473  if (find_section(default_toplevel, id) == nullptr) {
1474  // This section does not exist referenced from the
1475  // toplevel. Hence, add it to the hidden ones if it
1476  // is not referenced from another section.
1477  if (!section_is_referenced(id, *help_config)) {
1478  if (!ss.str().empty()) {
1479  ss << ",";
1480  }
1481  ss << id;
1482  }
1483  }
1484  }
1485  hidden_toplevel["sections"] = ss.str();
1486  ss.str("");
1487  for (const config &topic : help_config->child_range("topic"))
1488  {
1489  const std::string id = topic["id"];
1490  if (find_topic(default_toplevel, id) == nullptr) {
1491  if (!topic_is_referenced(id, *help_config)) {
1492  if (!ss.str().empty()) {
1493  ss << ",";
1494  }
1495  ss << id;
1496  }
1497  }
1498  }
1499  hidden_toplevel["topics"] = ss.str();
1500  config hidden_cfg = *help_config;
1501  // Change the toplevel to our new, custom built one.
1502  hidden_cfg.clear_children("toplevel");
1503  hidden_cfg.add_child("toplevel", std::move(hidden_toplevel));
1504  hidden_sections = parse_config(&hidden_cfg);
1505  }
1506  catch (parse_error& e) {
1507  std::stringstream msg;
1508  msg << "Parse error when parsing help text: '" << e.message << "'";
1509  std::cerr << msg.str() << std::endl;
1510  }
1511  }
1512 }
1513 
1514 // id starting with '.' are hidden
1515 std::string hidden_symbol(bool hidden) {
1516  return (hidden ? "." : "");
1517 }
1518 
1519 bool is_visible_id(const std::string &id) {
1520  return (id.empty() || id[0] != '.');
1521 }
1522 
1523 /**
1524  * Return true if the id is valid for user defined topics and
1525  * sections. Some IDs are special, such as toplevel and may not be
1526  * be defined in the config.
1527  */
1528 bool is_valid_id(const std::string &id) {
1529  if (id == "toplevel") {
1530  return false;
1531  }
1532  if (id.compare(0, unit_prefix.length(), unit_prefix) == 0 || id.compare(hidden_symbol().length(), unit_prefix.length(), unit_prefix) == 0) {
1533  return false;
1534  }
1535  if (id.compare(0, 8, "ability_") == 0) {
1536  return false;
1537  }
1538  if (id.compare(0, 14, "weaponspecial_") == 0) {
1539  return false;
1540  }
1541  if (id == "hidden") {
1542  return false;
1543  }
1544  return true;
1545 }
1546 
1547 
1548 // Return the width for the image with filename.
1549 unsigned image_width(const std::string &filename)
1550 {
1551  image::locator loc(filename);
1552  surface surf(image::get_image(loc));
1553  if (surf != nullptr) {
1554  return surf->w;
1555  }
1556  return 0;
1557 }
1558 
1559 void push_tab_pair(std::vector<help::item> &v, const std::string &s, const std::optional<std::string> &image, unsigned padding)
1560 {
1561  help::item item(s, font::pango_line_width(s, normal_font_size));
1562  if (image) {
1563  // If the image doesn't exist, don't add padding.
1564  auto width = image_width(*image);
1565  padding = (width ? padding : 0);
1566 
1567  item.first = "<img>src='" + *image + "'</img>" + (padding ? jump(padding) : "") + s;
1568  item.second += width + padding;
1569  }
1570  v.emplace_back(item);
1571 }
1572 
1573 std::string generate_table(const table_spec &tab, const unsigned int spacing)
1574 {
1575  table_spec::const_iterator row_it;
1576  std::vector<std::pair<std::string, unsigned>>::const_iterator col_it;
1577  unsigned int num_cols = 0;
1578  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1579  if (row_it->size() > num_cols) {
1580  num_cols = row_it->size();
1581  }
1582  }
1583  std::vector<unsigned int> col_widths(num_cols, 0);
1584  // Calculate the width of all columns, including spacing.
1585  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1586  unsigned int col = 0;
1587  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
1588  if (col_widths[col] < col_it->second + spacing) {
1589  col_widths[col] = col_it->second + spacing;
1590  }
1591  ++col;
1592  }
1593  }
1594  std::vector<unsigned int> col_starts(num_cols);
1595  // Calculate the starting positions of all columns
1596  for (unsigned int i = 0; i < num_cols; ++i) {
1597  unsigned int this_col_start = 0;
1598  for (unsigned int j = 0; j < i; ++j) {
1599  this_col_start += col_widths[j];
1600  }
1601  col_starts[i] = this_col_start;
1602  }
1603  std::stringstream ss;
1604  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1605  unsigned int col = 0;
1606  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
1607  ss << jump_to(col_starts[col]) << col_it->first;
1608  ++col;
1609  }
1610  ss << "\n";
1611  }
1612  return ss.str();
1613 }
1614 
1615 /** Prepend all chars with meaning inside attributes with a backslash. */
1616 std::string escape(const std::string &s)
1617 {
1618  return utils::escape(s, "'\\");
1619 }
1620 
1621 /** Load the appropriate terrain types data to use */
1622 std::shared_ptr<terrain_type_data> load_terrain_types_data()
1623 {
1624  if (display::get_singleton()) {
1626  } else if (game_config_manager::get()){
1628  } else {
1629  return {};
1630  }
1631 }
1632 
1633 
1634 } // end namespace help
std::string jump_to(const unsigned pos)
Definition: help_impl.hpp:388
surface get_image(const image::locator &i_locator, TYPE type)
Caches and returns an image.
Definition: picture.cpp:816
bool empty() const
Tests for an attribute that either was never set or was set to "".
const std::string ability_prefix
Definition: help_impl.cpp:96
section parse_config(const config *cfg)
Parse a help config, return the top level section.
Definition: help_impl.cpp:306
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
std::string id
Definition: help_impl.hpp:164
::tod_manager * tod_manager
Definition: resources.cpp:30
Ignore this unit for documentation purposes.
Definition: help_impl.hpp:242
std::string make_unit_link(const std::string &type_id)
return a hyperlink with the unit&#39;s name and pointing to the unit page return empty string if this uni...
Definition: help_impl.cpp:787
const std::string open_section_img
Definition: help_impl.cpp:86
int get_max_liminal_bonus() const
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:92
std::vector< topic > generate_trait_topics(const bool sort_generated)
Definition: help_impl.cpp:724
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
std::vector< topic > generate_unit_topics(const bool sort_generated, const std::string &race)
Definition: help_impl.cpp:976
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
const std::string era_prefix
Definition: help_impl.cpp:94
const std::string topic_img
Definition: help_impl.cpp:84
void clear_children(T... keys)
Definition: config.hpp:557
std::string era()
Definition: game.cpp:695
const int title_size
Definition: help_impl.cpp:79
bool is_overlay() const
Definition: terrain.hpp:155
config_array_view traits() const
Definition: types.hpp:400
const std::string closed_section_img
Definition: help_impl.cpp:85
std::string remove_first_space(const std::string &text)
Definition: help_impl.cpp:1420
bool is_scope_active(scope s)
A section contains topics and sections along with title and ID.
Definition: help_impl.hpp:146
Various functions that implement attacks and attack calculations.
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:885
const std::string unknown_unit_topic
Definition: help_impl.cpp:89
Variant for storing WML attributes.
const std::string race_prefix
Definition: help_impl.cpp:92
const color_t GOOD_COLOR
std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec, const std::vector< topic > &generated_topics)
Definition: help_impl.cpp:364
logger & info()
Definition: log.cpp:89
#define a
config_array_view child_range(config_key_type key) const
#define WRN_HP
Definition: help_impl.cpp:62
int compare(const std::string &s1, const std::string &s2)
Case-sensitive lexicographical comparison.
Definition: gettext.cpp:473
boost::tribool last_debug_state
Definition: help_impl.cpp:75
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
child_itors child_range(config_key_type key)
Definition: config.cpp:344
Thrown when the help system fails to parse something.
Definition: help_impl.hpp:209
bool operator<(const section &) const
Comparison on the ID.
Definition: help_impl.cpp:1200
std::vector< std::string > empty_string_vector
Definition: help_impl.cpp:77
Although the unit itself is hidden, traits reachable via this unit are not hidden.
Definition: help_impl.hpp:251
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 void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
int generic_combat_modifier(int lawful_bonus, unit_type::ALIGNMENT alignment, bool is_fearless, int max_liminal_bonus)
Returns the amount that a unit&#39;s damage should be multiplied by due to a given lawful_bonus.
Definition: attack.cpp:1612
const int SIZE_PLUS
Definition: constants.cpp:29
const race_map & races() const
Definition: types.hpp:399
const int normal_font_size
Definition: help_impl.cpp:82
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 last_num_encountered_terrains
Definition: help_impl.cpp:74
color_t string_to_color(const std::string &cmp_str)
Return the color the string represents.
Definition: help_impl.cpp:1377
const unit_type_map & types() const
Definition: types.hpp:398
bool is_visible_id(const std::string &id)
Definition: help_impl.cpp:1519
virtual const gamemap & map() const =0
bool hide_help() const
For instances created from a [terrain_type] tag, the value in the tag (with default false)...
Definition: terrain.hpp:61
std::vector< topic > generate_weapon_special_topics(const bool sort_generated)
Definition: help_impl.cpp:450
std::vector< std::string > make_unit_links_list(const std::vector< std::string > &type_id_list, bool ordered)
return a list of hyperlinks to unit&#39;s pages (ordered or not)
Definition: help_impl.cpp:813
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
void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
Dispatch generators to their appropriate functions.
Definition: help_impl.cpp:345
const int SIZE_NORMAL
Definition: constants.cpp:20
help::section hidden_sections
Definition: help_impl.cpp:71
const std::shared_ptr< terrain_type_data > & terrain_types() const
section_list sections
Definition: help_impl.hpp:166
const section * find_section(const section &sec, const std::string &id)
Search for the section with the specified identifier in the section and its subsections.
Definition: help_impl.cpp:1232
std::string id
Definition: help_impl.hpp:137
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:56
To be used as a function object to locate sections and topics with a specified ID.
Definition: help_impl.hpp:173
static game_config_manager * get()
const t_string & editor_name() const
Definition: terrain.hpp:49
const color_t BIGMAP_COLOR
The text displayed in a topic.
Definition: help_impl.hpp:82
std::string generate_table(const table_spec &tab, const unsigned int spacing)
Definition: help_impl.cpp:1573
const std::vector< unit_race::GENDER > & genders() const
The returned vector will not be empty, provided this has been built to the HELP_INDEXED status...
Definition: types.hpp:253
std::vector< std::vector< help::item > > table_spec
Definition: help_impl.hpp:412
std::string hidden_symbol(bool hidden)
Definition: help_impl.cpp:1515
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
std::string title
Definition: help_impl.hpp:164
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:141
static color_t from_hex_string(const std::string &c)
Creates a new color_t object from a string variable in hex format.
Definition: color.cpp:62
std::vector< topic > generate_era_topics(const bool sort_generated, const std::string &era_id)
Definition: help_impl.cpp:613
UNIT_DESCRIPTION_TYPE
Definition: help_impl.hpp:239
std::vector< topic > generate_faction_topics(const config &era, const bool sort_generated)
Definition: help_impl.cpp:649
std::set< t_translation::terrain_code > & encountered_terrains()
Definition: game.cpp:935
const std::vector< ability_metadata > & abilities_metadata() const
Definition: types.hpp:221
std::string race_id() const
Returns the ID of this type&#39;s race without the need to build the type.
Definition: types.hpp:276
std::string pango_word_wrap(const std::string &unwrapped_text, int font_size, int max_width, int max_height, int max_lines, bool)
Uses Pango to word wrap text.
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:144
static std::string time_of_day_bonus_colored(const int time_of_day_bonus)
Definition: help_impl.cpp:398
map_display and display: classes which take care of displaying the map and game-data on the screen...
std::vector< std::string > parsed_text_
Definition: help_impl.hpp:84
const std::shared_ptr< terrain_type_data > & tdata() const
Definition: map.hpp:204
static lg::log_domain log_help("help")
static bool is_cjk_char(const char32_t ch)
Definition: help_impl.cpp:98
const color_t YELLOW_COLOR
bool show_all_units_in_help()
Definition: game.cpp:920
const config & child_or_empty(config_key_type key) const
const color_t NORMAL_COLOR
std::shared_ptr< topic_generator > generator_
Definition: help_impl.hpp:85
Generic locator abstracting the location of an image.
Definition: picture.hpp:60
void parse_config_internal(const config *help_cfg, const config *section_cfg, section &sec, int level)
Recursive function used by parse_config.
Definition: help_impl.cpp:201
const int box_width
Definition: help_impl.cpp:81
std::string escape(const std::string &str, const char *special_chars)
Prepends a configurable set of characters with a backslash.
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:206
bool section_is_referenced(const std::string &section_id, const config &cfg)
Return true if the section with id section_id is referenced from another section in the config...
Definition: help_impl.cpp:153
bool topic_is_referenced(const std::string &topic_id, const config &cfg)
Return true if the topic with id topic_id is referenced from another section in the config...
Definition: help_impl.cpp:177
void generate_unit_sections(const config *, section &sec, int, const bool, const std::string &race)
Definition: help_impl.cpp:943
To be used as a function object when sorting section lists on the title.
Definition: help_impl.hpp:193
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
config::const_child_itors modification_advancements() const
Returns two iterators pointing to a range of AMLA configs.
Definition: types.hpp:123
const unit_race * find_race(const std::string &) const
Definition: types.cpp:1368
const color_t BLACK_COLOR
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
Thrown by operations encountering invalid UTF-8 data.
void generate_terrain_sections(section &sec, int)
Definition: help_impl.cpp:891
int last_num_encountered_units
Definition: help_impl.cpp:73
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:382
std::string convert_to_wml(const std::string &element_name, const std::string &contents)
Convert the contents to wml attributes, surrounded within [element_name]...[/element_name].
Definition: help_impl.cpp:1326
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
const std::vector< time_of_day > & times(const map_location &loc=map_location::null_location()) const
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
double g
Definition: astarsearch.cpp:65
const display_context & get_disp_context() const
Definition: display.hpp:172
const game_config_view * game_cfg
Definition: help_impl.cpp:67
std::set< std::string > & encountered_units()
Definition: game.cpp:930
const bool & debug
bool operator<(const topic &) const
Comparison on the ID.
Definition: help_impl.cpp:1190
void generate_races_sections(const config *help_cfg, section &sec, int level)
Definition: help_impl.cpp:828
const std::string unicode_bullet
Definition: constants.cpp:47
static int sort(lua_State *L)
Definition: ltablib.cpp:397
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static iterator_base end(const string_type &str)
const std::vector< ability_metadata > & adv_abilities_metadata() const
Some extra abilities that may be gained through AMLA advancements.
Definition: types.hpp:224
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
std::string escape(const std::string &s)
Prepend all chars with meaning inside attributes with a backslash.
Definition: help_impl.cpp:1616
std::vector< std::string > parse_text(const std::string &text)
Parse a text string.
Definition: help_impl.cpp:1253
config & add_child(config_key_type key)
Definition: config.cpp:514
const config & find_child(config_key_type key, const std::string &name, const std::string &value) const
REMOVE_EMPTY: remove empty elements.
const unsigned max_history
Definition: help_impl.cpp:83
const int SIZE_LARGE
Definition: constants.cpp:30
std::vector< topic > generate_time_of_day_topics(const bool)
Definition: help_impl.cpp:403
config::const_child_itors possible_traits() const
Definition: types.hpp:235
static lg::log_domain log_display("display")
#define DBG_HP
Definition: help_impl.cpp:63
std::string generate_contents_links(const std::string &section_name, config const *help_cfg)
Definition: help_impl.cpp:1127
const std::string faction_prefix
Definition: help_impl.cpp:93
std::string jump(const unsigned amount)
Definition: help_impl.hpp:395
#define f
double t
Definition: astarsearch.cpp:65
const std::string default_show_topic
Definition: help_impl.cpp:88
const unit_type & get_variation(const std::string &id) const
Definition: types.cpp:477
void generate_contents()
Generate the help contents from the configurations given to the manager.
Definition: help_impl.cpp:1454
std::vector< std::string > split(const config_attribute_value &val)
std::vector< topic > generate_topics(const bool sort_generated, const std::string &generator)
Definition: help_impl.cpp:316
bool operator==(const topic &) const
Two topics are equal if their IDs are equal.
Definition: help_impl.cpp:1185
const topic * find_topic(const section &sec, const std::string &id)
Search for the topic with the specified identifier in the section and its subsections.
Definition: help_impl.cpp:1216
const t_string & variation_name() const
Definition: types.hpp:177
const std::vector< std::string > & parsed_text() const
Definition: help_impl.cpp:388
Functions to load and save images from/to disk.
Standard logging facilities (interface).
const std::string & id() const
Definition: terrain.hpp:52
std::vector< terrain_code > ter_list
Definition: translation.hpp:78
std::string message
Definition: exceptions.hpp:30
const int title2_size
Definition: help_impl.cpp:80
A topic contains a title, an id and some text.
Definition: help_impl.hpp:112
#define e
Definition: help.cpp:57
const_attack_itors attacks() const
Definition: types.cpp:546
std::string title
Definition: help_impl.hpp:137
bool is_valid_id(const std::string &id)
Return true if the id is valid for user defined topics and sections.
Definition: help_impl.cpp:1528
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
static std::string alignment_description(ALIGNMENT align, unit_race::GENDER gender=unit_race::MALE)
mock_char c
const std::string & str() const
Definition: tstring.hpp:191
std::string get_first_word(const std::string &s)
Return the first word in s, not removing any spaces in the start of it.
Definition: help_impl.cpp:1428
ALIGNMENT alignment() const
Definition: types.hpp:197
void add_section(const section &s)
Allocate memory for and add the section.
Definition: help_impl.cpp:1205
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
std::vector< topic > generate_ability_topics(const bool sort_generated)
Definition: help_impl.cpp:547
void generate_era_sections(const config *help_cfg, section &sec, int level)
Definition: help_impl.cpp:868
Transitional API for porting SDL_ttf-based code to Pango.
Defines the MAKE_ENUM macro.
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
const int max_section_level
Definition: help_impl.cpp:78
topic_text & operator=(topic_text &&t)=default
To be used as a function object when sorting topic lists on the title.
Definition: help_impl.hpp:185
std::vector< std::string > split_in_width(const std::string &s, const int font_size, const unsigned width)
Make a best effort to word wrap s.
Definition: help_impl.cpp:1401
std::string debug() const
Definition: config.cpp:1347
topic_list topics
Definition: help_impl.hpp:165
topic_text text
Definition: help_impl.hpp:138
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
const color_t BAD_COLOR
bool operator==(const section &) const
Two sections are equal if their IDs are equal.
Definition: help_impl.cpp:1195
help::section default_toplevel
Definition: help_impl.cpp:69
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.