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