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