The Battle for Wesnoth  1.17.21+dev
help_impl.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 "about.hpp" // for get_text
19 #include "actions/attack.hpp" // for time_of_day bonus
20 #include "display.hpp" // for display
21 #include "display_context.hpp" // for display_context
22 #include "formula/string_utils.hpp" // for VGETTEXT
23 #include "game_config.hpp" // for debug, menu_contract, etc
24 #include "game_config_manager.hpp" // for game_config_manager
25 #include "preferences/game.hpp" // for encountered_terrains, etc
26 #include "gettext.hpp" // for _, gettext, N_
28 #include "hotkey/hotkey_command.hpp" // for is_scope_active, etc
29 #include "picture.hpp" // for get_image, locator
30 #include "log.hpp" // for LOG_STREAM, logger, etc
31 #include "map/map.hpp" // for gamemap
32 #include "font/standard_colors.hpp" // for NORMAL_COLOR
33 #include "font/sdl_ttf_compat.hpp"
34 #include "units/race.hpp" // for unit_race, etc
35 #include "resources.hpp" // for tod_manager, config_manager
36 #include "sdl/surface.hpp" // for surface
37 #include "serialization/string_utils.hpp" // for split, quoted_split, etc
38 #include "serialization/unicode_cast.hpp" // for unicode_cast
39 #include "serialization/utf8_exception.hpp" // for char_t, etc
40 #include "terrain/terrain.hpp" // for terrain_type
41 #include "terrain/translation.hpp" // for operator==, ter_list, etc
42 #include "terrain/type_data.hpp" // for terrain_type_data, etc
43 #include "time_of_day.hpp" // for time_of_day
44 #include "tod_manager.hpp" // for tod_manager
45 #include "tstring.hpp" // for t_string, operator<<
46 #include "units/types.hpp" // for unit_type, unit_type_data, etc
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  for (const t_translation::terrain_code& base : base_terrains) {
995 
996  const terrain_type& base_info = tdata->get_terrain_info(base);
997 
998  if (!base_info.is_nonnull() || base_info.hide_help())
999  continue;
1000 
1001  section& base_section = base_map[base_info.id()];
1002 
1003  base_section.id = terrain_prefix + base_info.id();
1004  base_section.title = base_info.editor_name();
1005 
1006  if (base_info.id() == info.id())
1007  terrain_topic.id = ".." + terrain_prefix + info.id();
1008  base_section.topics.push_back(terrain_topic);
1009  }
1010  }
1011 
1012  for (const auto& base : base_map) {
1013  sec.add_section(base.second);
1014  }
1015 }
1016 
1017 void generate_unit_sections(const config* /*help_cfg*/, section& sec, int /*level*/, const bool /*sort_generated*/, const std::string& race)
1018 {
1019  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types()) {
1020  const unit_type &type = i.second;
1021 
1022  if (type.race_id() != race)
1023  continue;
1024 
1025  if (!type.show_variations_in_help())
1026  continue;
1027 
1028  section base_unit;
1029  for (const std::string &variation_id : type.variations()) {
1030  // TODO: Do we apply encountered stuff to variations?
1031  const unit_type &var_type = type.get_variation(variation_id);
1032  const std::string topic_name = var_type.type_name() + "\n" + var_type.variation_name();
1033  const std::string var_ref = hidden_symbol(var_type.hide_help()) + variation_prefix + var_type.id() + "_" + variation_id;
1034 
1035  topic var_topic(topic_name, var_ref, "");
1036  var_topic.text = std::make_shared<unit_topic_generator>(var_type, variation_id);
1037  base_unit.topics.push_back(var_topic);
1038  }
1039 
1040  const std::string type_name = type.type_name();
1041  const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix + type.id();
1042 
1043  base_unit.id = ref_id;
1044  base_unit.title = type_name;
1045 
1046  sec.add_section(base_unit);
1047  }
1048 }
1049 
1050 std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
1051 {
1052  std::vector<topic> topics;
1053  std::set<std::string, string_less> race_units;
1054  std::set<std::string, string_less> race_topics;
1055  std::set<std::string> alignments;
1056 
1057  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
1058  {
1059  const unit_type &type = i.second;
1060 
1061  if (type.race_id() != race)
1062  continue;
1063 
1065  if (desc_type != FULL_DESCRIPTION)
1066  continue;
1067 
1068  const std::string debug_suffix = (game_config::debug ? " (" + type.id() + ")" : "");
1069  const std::string type_name = type.type_name() + (type.id() == type.type_name().str() ? "" : debug_suffix);
1070  const std::string real_prefix = type.show_variations_in_help() ? ".." : "";
1071  const std::string ref_id = hidden_symbol(type.hide_help()) + real_prefix + unit_prefix + type.id();
1072  topic unit_topic(type_name, ref_id, "");
1073  unit_topic.text = std::make_shared<unit_topic_generator>(type);
1074  topics.push_back(unit_topic);
1075 
1076  if (!type.hide_help()) {
1077  // we also record an hyperlink of this unit
1078  // in the list used for the race topic
1079  std::string link = make_link(type_name, ref_id);
1080  race_units.insert(link);
1081 
1082  alignments.insert(make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
1083  }
1084  }
1085 
1086  //generate the hidden race description topic
1087  std::string race_id = "..race_"+race;
1088  std::string race_name;
1089  std::string race_description;
1090  std::string race_help_taxonomy;
1091  if (const unit_race *r = unit_types.find_race(race)) {
1092  race_name = r->plural_name();
1093  race_description = r->description();
1094  race_help_taxonomy = r->help_taxonomy();
1095  // if (description.empty()) description = _("No description Available");
1096  for (const config &additional_topic : r->additional_topics())
1097  {
1098  std::string id = additional_topic["id"];
1099  std::string title = additional_topic["title"];
1100  std::string text = additional_topic["text"];
1101  //topic additional_topic(title, id, text);
1102  topics.emplace_back(title,id,text);
1103  std::string link = make_link(title, id);
1104  race_topics.insert(link);
1105  }
1106  } else {
1107  race_name = _ ("race^Miscellaneous");
1108  // description = _("Here put the description of the Miscellaneous race");
1109  }
1110 
1111  // Find any other races whose [race]help_taxonomy points to the current race
1112  std::map<std::string, t_string> subgroups;
1113  for (const auto &r : unit_types.races()) {
1114  if (r.second.help_taxonomy() == race) {
1115  if (!r.second.plural_name().empty())
1116  subgroups[r.first] = r.second.plural_name();
1117  else
1118  subgroups[r.first] = r.first;
1119  }
1120  }
1121 
1122  std::stringstream text;
1123 
1124  if (!race_description.empty()) {
1125  text << race_description << "\n\n";
1126  }
1127 
1128  if (!alignments.empty()) {
1129  std::set<std::string>::iterator it = alignments.begin();
1130  text << (alignments.size() > 1 ? _("Alignments: ") : _("Alignment: ")) << *(it++);
1131  while(it != alignments.end()) {
1132  text << ", " << *(it++);
1133  }
1134  text << "\n\n";
1135  }
1136 
1137  if (!race_help_taxonomy.empty()) {
1138  utils::string_map symbols;
1139  symbols["topic_id"] = "..race_"+race_help_taxonomy;
1140  if (const unit_race *r = unit_types.find_race(race_help_taxonomy)) {
1141  symbols["help_taxonomy"] = r->plural_name();
1142  } else {
1143  // Fall back to using showing the race id for the race that we couldn't find.
1144  // Not great, but probably useful if UMC has a broken link.
1145  symbols["help_taxonomy"] = race_help_taxonomy;
1146  }
1147  // TRANSLATORS: this is expected to say "[Dunefolk are] a group of units, all of whom are Humans",
1148  // or "[Quenoth Elves are] a group of units, all of whom are Elves".
1149  text << VGETTEXT("This is a group of units, all of whom are <ref>dst='$topic_id' text='$help_taxonomy'</ref>.", symbols) << "\n\n";
1150  }
1151 
1152  if (!subgroups.empty()) {
1153  if (!race_help_taxonomy.empty()) {
1154  text << _("<header>text='Subgroups of units within this group'</header>") << "\n";
1155  } else {
1156  text << _("<header>text='Groups of units within this race'</header>") << "\n";
1157  }
1158  for (const auto &sg : subgroups) {
1159  text << font::unicode_bullet << " " << make_link(sg.second, "..race_" + sg.first) << "\n";
1160  }
1161  text << "\n";
1162  }
1163 
1164  if (!race_help_taxonomy.empty()) {
1165  text << _("<header>text='Units of this group'</header>") << "\n";
1166  } else {
1167  text << _("<header>text='Units of this race'</header>") << "\n";
1168  }
1169  for (const auto &u : race_units) {
1170  text << font::unicode_bullet << " " << u << "\n";
1171  }
1172 
1173  topics.emplace_back(race_name, race_id, text.str());
1174 
1175  if (sort_generated)
1176  std::sort(topics.begin(), topics.end(), title_less());
1177 
1178  return topics;
1179 }
1180 
1182 {
1185  return FULL_DESCRIPTION;
1186  }
1187 
1188  const std::set<std::string> &encountered_units = preferences::encountered_units();
1189  if (encountered_units.find(type.id()) != encountered_units.end()) {
1190  return FULL_DESCRIPTION;
1191  }
1192 
1193  // See the docs of HIDDEN_BUT_SHOW_MACROS
1194  if (type.id() == "Fog Clearer") {
1195  return HIDDEN_BUT_SHOW_MACROS;
1196  }
1197 
1198  return NO_DESCRIPTION;
1199 }
1200 
1201 std::string generate_contents_links(const std::string& section_name, config const *help_cfg)
1202 {
1203  auto section_cfg = help_cfg->find_child("section", "id", section_name);
1204  if (!section_cfg) {
1205  return std::string();
1206  }
1207 
1208  std::ostringstream res;
1209 
1210  std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);
1211 
1212  // we use an intermediate structure to allow a conditional sorting
1213  typedef std::pair<std::string,std::string> link;
1214  std::vector<link> topics_links;
1215 
1217  // Find all topics in this section.
1218  for (t = topics.begin(); t != topics.end(); ++t) {
1219  if (auto topic_cfg = help_cfg->find_child("topic", "id", *t)) {
1220  std::string id = topic_cfg["id"];
1221  if (is_visible_id(id))
1222  topics_links.emplace_back(topic_cfg["title"], id);
1223  }
1224  }
1225 
1226  if (section_cfg["sort_topics"] == "yes") {
1227  std::sort(topics_links.begin(),topics_links.end());
1228  }
1229 
1231  for (l = topics_links.begin(); l != topics_links.end(); ++l) {
1232  std::string link = make_link(l->first, l->second);
1233  res << font::unicode_bullet << " " << link << "\n";
1234  }
1235 
1236  return res.str();
1237 }
1238 
1239 std::string generate_contents_links(const section &sec, const std::vector<topic>& topics)
1240 {
1241  std::stringstream res;
1242 
1243  for (auto &s : sec.sections) {
1244  if (is_visible_id(s.id)) {
1245  std::string link = make_link(s.title, ".."+s.id);
1246  res << font::unicode_bullet << " " << link << "\n";
1247  }
1248  }
1249 
1250  for (auto &t : topics) {
1251  if (is_visible_id(t.id)) {
1252  std::string link = make_link(t.title, t.id);
1253  res << font::unicode_bullet << " " << link << "\n";
1254  }
1255  }
1256  return res.str();
1257 }
1258 
1259 bool topic::operator==(const topic &t) const
1260 {
1261  return t.id == id;
1262 }
1263 
1264 bool topic::operator<(const topic &t) const
1265 {
1266  return id < t.id;
1267 }
1268 
1269 bool section::operator==(const section &sec) const
1270 {
1271  return sec.id == id;
1272 }
1273 
1274 bool section::operator<(const section &sec) const
1275 {
1276  return id < sec.id;
1277 }
1278 
1280 {
1281  sections.emplace_back(s);
1282 }
1283 
1285 {
1286  topics.clear();
1287  sections.clear();
1288 }
1289 
1290 const topic *find_topic(const section &sec, const std::string &id)
1291 {
1292  topic_list::const_iterator tit =
1293  std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
1294  if (tit != sec.topics.end()) {
1295  return &(*tit);
1296  }
1297  for (const auto &s : sec.sections) {
1298  const auto t = find_topic(s, id);
1299  if (t != nullptr) {
1300  return t;
1301  }
1302  }
1303  return nullptr;
1304 }
1305 
1306 const section *find_section(const section &sec, const std::string &id)
1307 {
1308  const auto &sit =
1309  std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
1310  if (sit != sec.sections.end()) {
1311  return &*sit;
1312  }
1313  for (const auto &subsection : sec.sections) {
1314  const auto s = find_section(subsection, id);
1315  if (s != nullptr) {
1316  return s;
1317  }
1318  }
1319  return nullptr;
1320 }
1321 
1322 section *find_section(section &sec, const std::string &id)
1323 {
1324  return const_cast<section *>(find_section(const_cast<const section &>(sec), id));
1325 }
1326 
1327 std::vector<std::string> parse_text(const std::string &text)
1328 {
1329  std::vector<std::string> res;
1330  bool last_char_escape = false;
1331  const char escape_char = '\\';
1332  std::stringstream ss;
1333  std::size_t pos;
1334  enum { ELEMENT_NAME, OTHER } state = OTHER;
1335  for (pos = 0; pos < text.size(); ++pos) {
1336  const char c = text[pos];
1337  if (c == escape_char && !last_char_escape) {
1338  last_char_escape = true;
1339  }
1340  else {
1341  if (state == OTHER) {
1342  if (c == '<') {
1343  if (last_char_escape) {
1344  ss << c;
1345  }
1346  else {
1347  res.push_back(ss.str());
1348  ss.str("");
1349  state = ELEMENT_NAME;
1350  }
1351  }
1352  else {
1353  ss << c;
1354  }
1355  }
1356  else if (state == ELEMENT_NAME) {
1357  if (c == '/') {
1358  std::string msg = "Erroneous / in element name.";
1359  throw parse_error(msg);
1360  }
1361  else if (c == '>') {
1362  // End of this name.
1363  std::stringstream s;
1364  const std::string element_name = ss.str();
1365  ss.str("");
1366  s << "</" << element_name << ">";
1367  const std::string end_element_name = s.str();
1368  std::size_t end_pos = text.find(end_element_name, pos);
1369  if (end_pos == std::string::npos) {
1370  std::stringstream msg;
1371  msg << "Unterminated element: " << element_name;
1372  throw parse_error(msg.str());
1373  }
1374  s.str("");
1375  const std::string contents = text.substr(pos + 1, end_pos - pos - 1);
1376  const std::string element = convert_to_wml(element_name, contents);
1377  res.push_back(element);
1378  pos = end_pos + end_element_name.size() - 1;
1379  state = OTHER;
1380  }
1381  else {
1382  ss << c;
1383  }
1384  }
1385  last_char_escape = false;
1386  }
1387  }
1388  if (state == ELEMENT_NAME) {
1389  std::stringstream msg;
1390  msg << "Element '" << ss.str() << "' continues through end of string.";
1391  throw parse_error(msg.str());
1392  }
1393  if (!ss.str().empty()) {
1394  // Add the last string.
1395  res.push_back(ss.str());
1396  }
1397  return res;
1398 }
1399 
1400 std::string convert_to_wml(const std::string &element_name, const std::string &contents)
1401 {
1402  std::stringstream ss;
1403  bool in_quotes = false;
1404  bool last_char_escape = false;
1405  const char escape_char = '\\';
1406  std::vector<std::string> attributes;
1407  // Find the different attributes.
1408  // No checks are made for the equal sign or something like that.
1409  // Attributes are just separated by spaces or newlines.
1410  // Attributes that contain spaces must be in single quotes.
1411  for (std::size_t pos = 0; pos < contents.size(); ++pos) {
1412  const char c = contents[pos];
1413  if (c == escape_char && !last_char_escape) {
1414  last_char_escape = true;
1415  }
1416  else {
1417  if (c == '\'' && !last_char_escape) {
1418  ss << '"';
1419  in_quotes = !in_quotes;
1420  }
1421  else if ((c == ' ' || c == '\n') && !last_char_escape && !in_quotes) {
1422  // Space or newline, end of attribute.
1423  attributes.push_back(ss.str());
1424  ss.str("");
1425  }
1426  else {
1427  ss << c;
1428  }
1429  last_char_escape = false;
1430  }
1431  }
1432  if (in_quotes) {
1433  std::stringstream msg;
1434  msg << "Unterminated single quote after: '" << ss.str() << "'";
1435  throw parse_error(msg.str());
1436  }
1437  if (!ss.str().empty()) {
1438  attributes.push_back(ss.str());
1439  }
1440  ss.str("");
1441  // Create the WML.
1442  ss << "[" << element_name << "]\n";
1443  for (std::vector<std::string>::const_iterator it = attributes.begin();
1444  it != attributes.end(); ++it) {
1445  ss << *it << "\n";
1446  }
1447  ss << "[/" << element_name << "]\n";
1448  return ss.str();
1449 }
1450 
1451 color_t string_to_color(const std::string &cmp_str)
1452 {
1453  if (cmp_str == "green") {
1454  return font::GOOD_COLOR;
1455  }
1456  if (cmp_str == "red") {
1457  return font::BAD_COLOR;
1458  }
1459  if (cmp_str == "black") {
1460  return font::BLACK_COLOR;
1461  }
1462  if (cmp_str == "yellow") {
1463  return font::YELLOW_COLOR;
1464  }
1465  if (cmp_str == "white") {
1466  return font::BIGMAP_COLOR;
1467  }
1468  // a #rrggbb color in pango format.
1469  if (*cmp_str.c_str() == '#' && cmp_str.size() == 7) {
1470  return color_t::from_hex_string(cmp_str.substr(1));
1471  }
1472  return font::NORMAL_COLOR;
1473 }
1474 
1475 std::vector<std::string> split_in_width(const std::string &s, const int font_size,
1476  const unsigned width)
1477 {
1478  std::vector<std::string> res;
1479  try {
1480  const std::string& first_line = font::pango_word_wrap(s, font_size, width, -1, 1, true);
1481  res.push_back(first_line);
1482  if(s.size() > first_line.size()) {
1483  res.push_back(s.substr(first_line.size()));
1484  }
1485  }
1487  {
1488  throw parse_error (_("corrupted original file"));
1489  }
1490 
1491  return res;
1492 }
1493 
1494 std::string remove_first_space(const std::string& text)
1495 {
1496  if (text.length() > 0 && text[0] == ' ') {
1497  return text.substr(1);
1498  }
1499  return text;
1500 }
1501 
1502 std::string get_first_word(const std::string &s)
1503 {
1504  std::size_t first_word_start = s.find_first_not_of(' ');
1505  if (first_word_start == std::string::npos) {
1506  return s;
1507  }
1508  std::size_t first_word_end = s.find_first_of(" \n", first_word_start);
1509  if( first_word_end == first_word_start ) {
1510  // This word is '\n'.
1511  first_word_end = first_word_start+1;
1512  }
1513 
1514  //if no gap(' ' or '\n') found, test if it is CJK character
1515  std::string re = s.substr(0, first_word_end);
1516 
1517  utf8::iterator ch(re);
1518  if (ch == utf8::iterator::end(re))
1519  return re;
1520 
1521  char32_t firstchar = *ch;
1522  if (is_cjk_char(firstchar)) {
1523  re = unicode_cast<std::string>(firstchar);
1524  }
1525  return re;
1526 }
1527 
1529 {
1532  if (game_cfg != nullptr) {
1533  const config *help_config = &game_cfg->child_or_empty("help");
1534  try {
1535  default_toplevel = parse_config(help_config);
1536  // Create a config object that contains everything that is
1537  // not referenced from the toplevel element. Read this
1538  // config and save these sections and topics so that they
1539  // can be referenced later on when showing help about
1540  // specified things, but that should not be shown when
1541  // opening the help browser in the default manner.
1542  config hidden_toplevel;
1543  std::stringstream ss;
1544  for (const config &section : help_config->child_range("section"))
1545  {
1546  const std::string id = section["id"];
1547  if (find_section(default_toplevel, id) == nullptr) {
1548  // This section does not exist referenced from the
1549  // toplevel. Hence, add it to the hidden ones if it
1550  // is not referenced from another section.
1551  if (!section_is_referenced(id, *help_config)) {
1552  if (!ss.str().empty()) {
1553  ss << ",";
1554  }
1555  ss << id;
1556  }
1557  }
1558  }
1559  hidden_toplevel["sections"] = ss.str();
1560  ss.str("");
1561  for (const config &topic : help_config->child_range("topic"))
1562  {
1563  const std::string id = topic["id"];
1564  if (find_topic(default_toplevel, id) == nullptr) {
1565  if (!topic_is_referenced(id, *help_config)) {
1566  if (!ss.str().empty()) {
1567  ss << ",";
1568  }
1569  ss << id;
1570  }
1571  }
1572  }
1573  hidden_toplevel["topics"] = ss.str();
1574  config hidden_cfg = *help_config;
1575  // Change the toplevel to our new, custom built one.
1576  hidden_cfg.clear_children("toplevel");
1577  hidden_cfg.add_child("toplevel", std::move(hidden_toplevel));
1578  hidden_sections = parse_config(&hidden_cfg);
1579  }
1580  catch (parse_error& e) {
1581  std::stringstream msg;
1582  msg << "Parse error when parsing help text: '" << e.message << "'";
1583  PLAIN_LOG << msg.str();
1584  }
1585  }
1586 }
1587 
1588 // id starting with '.' are hidden
1589 std::string hidden_symbol(bool hidden) {
1590  return (hidden ? "." : "");
1591 }
1592 
1593 bool is_visible_id(const std::string &id) {
1594  return (id.empty() || id[0] != '.');
1595 }
1596 
1597 /**
1598  * Return true if the id is valid for user defined topics and
1599  * sections. Some IDs are special, such as toplevel and may not be
1600  * be defined in the config.
1601  */
1602 bool is_valid_id(const std::string &id) {
1603  if (id == "toplevel") {
1604  return false;
1605  }
1606  if (id.compare(0, unit_prefix.length(), unit_prefix) == 0 || id.compare(hidden_symbol().length(), unit_prefix.length(), unit_prefix) == 0) {
1607  return false;
1608  }
1609  if (id.compare(0, 8, "ability_") == 0) {
1610  return false;
1611  }
1612  if (id.compare(0, 14, "weaponspecial_") == 0) {
1613  return false;
1614  }
1615  if (id == "hidden") {
1616  return false;
1617  }
1618  return true;
1619 }
1620 
1621 
1622 // Return the width for the image with filename.
1623 unsigned image_width(const std::string &filename)
1624 {
1625  image::locator loc(filename);
1626  surface surf(image::get_surface(loc));
1627  if (surf != nullptr) {
1628  return surf->w;
1629  }
1630  return 0;
1631 }
1632 
1633 void push_tab_pair(std::vector<help::item> &v, const std::string &s, const std::optional<std::string> &image, unsigned padding)
1634 {
1636  if (image) {
1637  // If the image doesn't exist, don't add padding.
1638  auto width = image_width(*image);
1639  padding = (width ? padding : 0);
1640 
1641  item.first = "<img>src='" + *image + "'</img>" + (padding ? jump(padding) : "") + s;
1642  item.second += width + padding;
1643  }
1644  v.emplace_back(item);
1645 }
1646 
1647 std::string generate_table(const table_spec &tab, const unsigned int spacing)
1648 {
1649  table_spec::const_iterator row_it;
1650  std::vector<std::pair<std::string, unsigned>>::const_iterator col_it;
1651  unsigned int num_cols = 0;
1652  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1653  if (row_it->size() > num_cols) {
1654  num_cols = row_it->size();
1655  }
1656  }
1657  std::vector<unsigned int> col_widths(num_cols, 0);
1658  // Calculate the width of all columns, including spacing.
1659  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1660  unsigned int col = 0;
1661  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
1662  if (col_widths[col] < col_it->second + spacing) {
1663  col_widths[col] = col_it->second + spacing;
1664  }
1665  ++col;
1666  }
1667  }
1668  std::vector<unsigned int> col_starts(num_cols);
1669  // Calculate the starting positions of all columns
1670  for (unsigned int i = 0; i < num_cols; ++i) {
1671  unsigned int this_col_start = 0;
1672  for (unsigned int j = 0; j < i; ++j) {
1673  this_col_start += col_widths[j];
1674  }
1675  col_starts[i] = this_col_start;
1676  }
1677  std::stringstream ss;
1678  for (row_it = tab.begin(); row_it != tab.end(); ++row_it) {
1679  unsigned int col = 0;
1680  for (col_it = row_it->begin(); col_it != row_it->end(); ++col_it) {
1681  ss << jump_to(col_starts[col]) << col_it->first;
1682  ++col;
1683  }
1684  ss << "\n";
1685  }
1686  return ss.str();
1687 }
1688 
1689 /** Prepend all chars with meaning inside attributes with a backslash. */
1690 std::string escape(const std::string &s)
1691 {
1692  return utils::escape(s, "'\\");
1693 }
1694 
1695 /** Load the appropriate terrain types data to use */
1696 std::shared_ptr<terrain_type_data> load_terrain_types_data()
1697 {
1698  if (display::get_singleton()) {
1700  } else if (game_config_manager::get()){
1702  } else {
1703  return {};
1704  }
1705 }
1706 
1707 
1708 } // 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:1606
Various functions that implement attacks and attack calculations.
double t
Definition: astarsearch.cpp:65
double g
Definition: astarsearch.cpp:65
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:161
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:791
void clear_children(T... keys)
Definition: config.hpp:644
child_itors child_range(config_key_type key)
Definition: config.cpp:277
std::string debug() const
Definition: config.cpp:1248
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:389
config & add_child(config_key_type key)
Definition: config.cpp:445
virtual const gamemap & map() const =0
const display_context & get_disp_context() const
Definition: display.hpp:189
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:101
static game_config_manager * get()
const std::shared_ptr< terrain_type_data > & terrain_types() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
const config & child_or_empty(config_key_type key) const
optional_const_config find_child(config_key_type key, const std::string &name, const std::string &value) const
config_array_view child_range(config_key_type key) const
const std::shared_ptr< terrain_type_data > & tdata() const
Definition: map.hpp:204
To be used as a function object to locate sections and topics with a specified ID.
Definition: help_impl.hpp:174
To be used as a function object when sorting section lists on the title.
Definition: help_impl.hpp:196
To be used as a function object when sorting topic lists on the title.
Definition: help_impl.hpp:186
The text displayed in a topic.
Definition: help_impl.hpp:83
const std::vector< std::string > & parsed_text() const
Definition: help_impl.cpp:385
std::shared_ptr< topic_generator > generator_
Definition: help_impl.hpp:85
topic_text & operator=(topic_text &&t)=default
std::vector< std::string > parsed_text_
Definition: help_impl.hpp:84
Generic locator abstracting the location of an image.
Definition: picture.hpp:64
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:1246
const race_map & races() const
Definition: types.hpp:397
const unit_race * find_race(const std::string &) const
Definition: types.cpp:1350
const unit_type_map & types() const
Definition: types.hpp:396
config_array_view traits() const
Definition: types.hpp:398
A single unit type that the player may recruit.
Definition: types.hpp:46
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:144
@ HELP_INDEXED
Definition: types.hpp:77
bool hide_help() const
Definition: types.cpp:624
const t_string & variation_name() const
Definition: types.hpp:177
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:141
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:215
#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:262
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:57
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:1502
std::vector< topic > generate_unit_topics(const bool sort_generated, const std::string &race)
Definition: help_impl.cpp:1050
std::string hidden_symbol(bool hidden)
Definition: help_impl.cpp:1589
void generate_unit_sections(const config *, section &sec, int, const bool, const std::string &race)
Definition: help_impl.cpp:1017
UNIT_DESCRIPTION_TYPE
Definition: help_impl.hpp:243
@ FULL_DESCRIPTION
Definition: help_impl.hpp:244
@ HIDDEN_BUT_SHOW_MACROS
Although the unit itself is hidden, traits reachable via this unit are not hidden.
Definition: help_impl.hpp:255
@ NO_DESCRIPTION
Ignore this unit for documentation purposes.
Definition: help_impl.hpp:246
std::string make_link(const std::string &text, const std::string &dst)
Definition: help_impl.hpp:386
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:1690
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:1623
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:1593
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:1181
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:416
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:1306
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:1633
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:1400
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
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:1528
std::string generate_table(const table_spec &tab, const unsigned int spacing)
Definition: help_impl.cpp:1647
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:1494
std::string jump_to(const unsigned pos)
Definition: help_impl.hpp:392
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:1696
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:1602
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:1201
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:1475
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:1290
color_t string_to_color(const std::string &cmp_str)
Return the color the string represents.
Definition: help_impl.cpp:1451
std::string jump(const unsigned amount)
Definition: help_impl.hpp:399
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:1327
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:686
logger & info()
Definition: log.cpp:238
bool show_all_units_in_help()
Definition: game.cpp:906
std::set< std::string > & encountered_units()
Definition: game.cpp:916
std::string era()
Definition: game.cpp:681
std::set< t_translation::terrain_code > & encountered_terrains()
Definition: game.cpp:921
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
::tod_manager * tod_manager
Definition: resources.cpp:30
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:504
@ 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:110
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:214
A section contains topics and sections along with title and ID.
Definition: help_impl.hpp:146
section_list sections
Definition: help_impl.hpp:166
bool operator<(const section &) const
Comparison on the ID.
Definition: help_impl.cpp:1274
void add_section(const section &s)
Allocate memory for and add the section.
Definition: help_impl.cpp:1279
std::string id
Definition: help_impl.hpp:164
std::string title
Definition: help_impl.hpp:164
bool operator==(const section &) const
Two sections are equal if their IDs are equal.
Definition: help_impl.cpp:1269
topic_list topics
Definition: help_impl.hpp:165
A topic contains a title, an id and some text.
Definition: help_impl.hpp:113
bool operator==(const topic &) const
Two topics are equal if their IDs are equal.
Definition: help_impl.cpp:1259
std::string id
Definition: help_impl.hpp:137
topic_text text
Definition: help_impl.hpp:138
bool operator<(const topic &) const
Comparison on the ID.
Definition: help_impl.cpp:1264
std::string title
Definition: help_impl.hpp:137
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
mock_char c
static map_location::DIRECTION s
unit_type_data unit_types
Definition: types.cpp:1465
#define e
#define f
#define a