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