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