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