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