The Battle for Wesnoth  1.19.17+dev
help_impl.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "help/help_impl.hpp"
17 
18 #include "actions/attack.hpp" // for time_of_day bonus
19 #include "color.hpp"
20 #include "formula/string_utils.hpp" // for VGETTEXT
21 #include "font/standard_colors.hpp" // for NORMAL_COLOR
22 #include "game_config.hpp" // for debug, menu_contract, etc
23 #include "game_config_manager.hpp" // for game_config_manager
24 #include "gettext.hpp" // for _, gettext, N_
26 #include "hotkey/hotkey_command.hpp" // for is_scope_active, etc
27 #include "log.hpp" // for LOG_STREAM, logger, etc
28 #include "map/map.hpp" // for gamemap
29 #include "picture.hpp" // for get_image, locator
30 #include "preferences/preferences.hpp" // for encountered_terrains, etc
31 #include "resources.hpp" // for tod_manager, config_manager
32 #include "serialization/markup.hpp" // for markup utility functions
33 #include "serialization/parser.hpp"
34 #include "serialization/string_utils.hpp" // for split, quoted_split, etc
35 #include "terrain/terrain.hpp" // for terrain_type
36 #include "terrain/translation.hpp" // for operator==, ter_list, etc
37 #include "terrain/type_data.hpp" // for terrain_type_data, etc
38 #include "time_of_day.hpp" // for time_of_day
39 #include "tod_manager.hpp" // for tod_manager
40 #include "tstring.hpp" // for t_string, operator<<
41 #include "units/race.hpp" // for unit_race, etc
42 #include "units/types.hpp" // for unit_type, unit_type_data, etc
43 #include "utils/general.hpp" // for contains
44 
45 #include <algorithm> // for sort, find, transform, etc
46 #include <boost/algorithm/string.hpp>
47 #include <cassert> // for assert
48 #include <iterator> // for back_insert_iterator, etc
49 #include <map> // for map, etc
50 #include <set>
51 #include <utility>
52 
53 static lg::log_domain log_help("help");
54 #define WRN_HP LOG_STREAM(warn, log_help)
55 #define DBG_HP LOG_STREAM(debug, log_help)
56 
57 namespace help {
58 
59 const int max_section_level = 15;
60 // The topic to open by default when opening the help dialog.
61 const std::string default_show_topic = "..introduction";
62 const std::string unknown_unit_topic = ".unknown_unit";
63 const std::string unit_prefix = "unit_";
64 const std::string terrain_prefix = "terrain_";
65 const std::string race_prefix = "race_";
66 const std::string faction_prefix = "faction_";
67 const std::string era_prefix = "era_";
68 const std::string variation_prefix = "variation_";
69 const std::string ability_prefix = "ability_";
70 
71 bool section_is_referenced(const std::string &section_id, const config &cfg)
72 {
73  if (auto toplevel = cfg.optional_child("toplevel"))
74  {
75  if(utils::contains(utils::quoted_split(toplevel["sections"]), section_id)) {
76  return true;
77  }
78  }
79 
80  for (const config &section : cfg.child_range("section"))
81  {
82  if(utils::contains(utils::quoted_split(section["sections"]), section_id)) {
83  return true;
84  }
85  }
86  return false;
87 }
88 
89 bool topic_is_referenced(const std::string &topic_id, const config &cfg)
90 {
91  if (auto toplevel = cfg.optional_child("toplevel"))
92  {
93  if(utils::contains(utils::quoted_split(toplevel["topics"]), topic_id)) {
94  return true;
95  }
96  }
97 
98  for (const config &section : cfg.child_range("section"))
99  {
100  if(utils::contains(utils::quoted_split(section["topics"]), topic_id)) {
101  return true;
102  }
103  }
104  return false;
105 }
106 
107 void parse_config_internal(const config *help_cfg, const config *section_cfg,
108  section &sec, int level)
109 {
110  if (level > max_section_level) {
111  PLAIN_LOG << "Maximum section depth has been reached. Maybe circular dependency?";
112  }
113  else if (section_cfg != nullptr) {
114  const std::vector<std::string> sections = utils::quoted_split((*section_cfg)["sections"]);
115  std::string id = level == 0 ? "toplevel" : (*section_cfg)["id"].str();
116  if (level != 0) {
117  if (!is_valid_id(id)) {
118  std::stringstream ss;
119  ss << "Invalid ID, used for internal purpose: '" << id << "'";
120  throw parse_error(ss.str());
121  }
122  }
123  std::string title = level == 0 ? "" : (*section_cfg)["title"].str();
124  sec.id = id;
125  sec.title = title;
126  // Find all child sections.
127  for(const std::string& sec_id : sections) {
128  if (auto child_cfg = help_cfg->find_child("section", "id", sec_id))
129  {
130  section child_section;
131  parse_config_internal(help_cfg, child_cfg.ptr(), child_section, level + 1);
132  sec.add_section(child_section);
133  }
134  else {
135  std::stringstream ss;
136  ss << "Help-section '" << sec_id << "' referenced from '"
137  << id << "' but could not be found.";
138  throw parse_error(ss.str());
139  }
140  }
141 
142  generate_sections(help_cfg, (*section_cfg)["sections_generator"], sec, level);
143  if ((*section_cfg)["sort_sections"] == "yes") {
144  sec.sections.sort(section_less());
145  }
146 
147  bool sort_topics = false;
148  bool sort_generated = true;
149 
150  if ((*section_cfg)["sort_topics"] == "yes") {
151  sort_topics = true;
152  sort_generated = false;
153  } else if ((*section_cfg)["sort_topics"] == "no") {
154  sort_topics = false;
155  sort_generated = false;
156  } else if ((*section_cfg)["sort_topics"] == "generated") {
157  sort_topics = false;
158  sort_generated = true;
159  } else if (!(*section_cfg)["sort_topics"].empty()) {
160  std::stringstream ss;
161  ss << "Invalid sort option: '" << (*section_cfg)["sort_topics"] << "'";
162  throw parse_error(ss.str());
163  }
164 
165  std::vector<topic> generated_topics = generate_topics(sort_generated,(*section_cfg)["generator"]);
166  std::vector<topic> topics;
167 
168  // Find all topics in this section.
169  for(const std::string& topic_id : utils::quoted_split((*section_cfg)["topics"])) {
170  if (auto topic_cfg = help_cfg->find_child("topic", "id", topic_id))
171  {
172  std::string text = topic_cfg["text"];
173  text += generate_topic_text(topic_cfg["generator"], help_cfg, sec);
174  topic child_topic(topic_cfg["title"], topic_cfg["id"], text);
175  if (!is_valid_id(child_topic.id)) {
176  std::stringstream ss;
177  ss << "Invalid ID, used for internal purpose: '" << id << "'";
178  throw parse_error(ss.str());
179  }
180  topics.push_back(child_topic);
181  }
182  else {
183  std::stringstream ss;
184  ss << "Help-topic '" << topic_id << "' referenced from '" << id
185  << "' but could not be found." << std::endl;
186  throw parse_error(ss.str());
187  }
188  }
189 
190  if (sort_topics) {
191  std::sort(topics.begin(),topics.end(), title_less());
192  std::sort(generated_topics.begin(),
193  generated_topics.end(), title_less());
194  std::merge(generated_topics.begin(),
195  generated_topics.end(),topics.begin(),topics.end()
196  ,std::back_inserter(sec.topics),title_less());
197  }
198  else {
199  sec.topics.insert(sec.topics.end(),
200  topics.begin(), topics.end());
201  sec.topics.insert(sec.topics.end(),
202  generated_topics.begin(), generated_topics.end());
203  }
204  }
205 }
206 
208 {
209  section sec;
210  if (cfg != nullptr) {
211  auto toplevel_cfg = cfg->optional_child("toplevel");
212  parse_config_internal(cfg, toplevel_cfg.ptr(), sec);
213  }
214  return sec;
215 }
216 
217 std::vector<topic> generate_topics(const bool sort_generated,const std::string &generator)
218 {
219  std::vector<topic> res;
220  if (generator.empty()) {
221  return res;
222  }
223 
224  if (generator == "abilities") {
225  res = generate_ability_topics(sort_generated);
226  } else if (generator == "weapon_specials") {
227  res = generate_weapon_special_topics(sort_generated);
228  } else if (generator == "time_of_days") {
229  res = generate_time_of_day_topics(sort_generated);
230  } else if (generator == "traits") {
231  res = generate_trait_topics(sort_generated);
232  } else {
233  std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
234  if (parts.size() > 1 && parts[0] == "units") {
235  res = generate_unit_topics(sort_generated, parts[1]);
236  } else if (parts[0] == "era" && parts.size()>1) {
237  res = generate_era_topics(sort_generated, parts[1]);
238  } else {
239  WRN_HP << "Found a topic generator that I didn't recognize: " << generator;
240  }
241  }
242 
243  return res;
244 }
245 
246 void generate_sections(const config *help_cfg, const std::string &generator, section &sec, int level)
247 {
248  if (generator == "races") {
249  generate_races_sections(help_cfg, sec, level);
250  } else if (generator == "terrains") {
252  } else if (generator == "eras") {
253  DBG_HP << "Generating eras...";
254  generate_era_sections(help_cfg, sec, level);
255  } else {
256  std::vector<std::string> parts = utils::split(generator, ':', utils::STRIP_SPACES);
257  if (parts.size() > 1 && parts[0] == "units") {
258  generate_unit_sections(help_cfg, sec, level, true, parts[1]);
259  } else if (generator.size() > 0) {
260  WRN_HP << "Found a section generator that I didn't recognize: " << generator;
261  }
262  }
263 }
264 
265 std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec)
266 {
267  std::string empty_string = "";
268  if (generator.empty()) {
269  return empty_string;
270  } else {
271  std::vector<std::string> parts = utils::split(generator, ':');
272  if (parts.size() > 1 && parts[0] == "contents") {
273  if (parts[1] == "generated") {
274  return generate_contents_links(sec);
275  } else {
276  return generate_contents_links(parts[1], help_cfg);
277  }
278  }
279  }
280  return empty_string;
281 }
282 
284 {
285  if (generator_) {
287  // This caches the result, so doesn't need the generator any more
288  generator_.reset();
289  }
290  return parsed_text_;
291 }
292 
293 static std::string time_of_day_bonus_colored(const int time_of_day_bonus)
294 {
295  return markup::span_color((time_of_day_bonus > 0 ? "green" : (time_of_day_bonus < 0 ? "red" : "white")), time_of_day_bonus);
296 }
297 
298 std::vector<topic> generate_time_of_day_topics(const bool /*sort_generated*/)
299 {
300  std::vector<topic> topics;
301  std::stringstream toplevel;
302 
303  if (!resources::tod_manager) {
304  toplevel << _("Only available during a scenario.");
305  topics.emplace_back(_("Time of Day Schedule"), "..schedule", toplevel.str());
306  return topics;
307  }
308 
309  const std::vector<time_of_day>& times = resources::tod_manager->times();
310  for (const time_of_day& time : times)
311  {
312  const std::string id = "time_of_day_" + time.id;
313  const std::string image = markup::img(time.image);
314  const std::string image_lawful = markup::img("icons/alignments/alignment_lawful_30.png");
315  const std::string image_neutral = markup::img("icons/alignments/alignment_neutral_30.png");
316  const std::string image_chaotic = markup::img("icons/alignments/alignment_chaotic_30.png");
317  const std::string image_liminal = markup::img("icons/alignments/alignment_liminal_30.png");
318  std::stringstream text, row_ss;
319 
320  const int lawful_bonus = generic_combat_modifier(time.lawful_bonus, unit_alignments::type::lawful, false, resources::tod_manager->get_max_liminal_bonus());
321  const int neutral_bonus = generic_combat_modifier(time.lawful_bonus, unit_alignments::type::neutral, false, resources::tod_manager->get_max_liminal_bonus());
322  const int chaotic_bonus = generic_combat_modifier(time.lawful_bonus, unit_alignments::type::chaotic, false, resources::tod_manager->get_max_liminal_bonus());
323  const int liminal_bonus = generic_combat_modifier(time.lawful_bonus, unit_alignments::type::liminal, false, resources::tod_manager->get_max_liminal_bonus());
324 
325  row_ss << markup::tag("col", markup::make_link(time.name.str(), id))
326  << markup::tag("col", image)
327  << markup::tag("col", image_lawful, time_of_day_bonus_colored(lawful_bonus))
328  << markup::tag("col", image_neutral, time_of_day_bonus_colored(neutral_bonus))
329  << markup::tag("col", image_chaotic, time_of_day_bonus_colored(chaotic_bonus))
330  << markup::tag("col", image_liminal, time_of_day_bonus_colored(liminal_bonus));
331  toplevel << markup::tag("row", row_ss.str());
332 
333  text << image << '\n'
334  << time.description.str() << '\n'
335  << image_lawful << _("Lawful Bonus:") << ' ' << time_of_day_bonus_colored(lawful_bonus) << '\n'
336  << image_neutral << _("Neutral Bonus:") << ' ' << time_of_day_bonus_colored(neutral_bonus) << '\n'
337  << image_chaotic << _("Chaotic Bonus:") << ' ' << time_of_day_bonus_colored(chaotic_bonus) << '\n'
338  << image_liminal << _("Liminal Bonus:") << ' ' << time_of_day_bonus_colored(liminal_bonus) << '\n' << '\n'
339  << markup::make_link(_("Schedule"), "..schedule");
340 
341  topics.emplace_back(time.name.str(), id, text.str());
342  }
343 
344  topics.emplace_back(_("Time of Day Schedule"), "..schedule", markup::tag("table", toplevel.str()));
345  return topics;
346 }
347 
348 std::vector<topic> generate_weapon_special_topics(const bool sort_generated)
349 {
350  std::vector<topic> topics;
351 
352  std::map<t_string, std::string> special_description;
353  std::map<t_string, std::set<std::string, string_less>> special_units;
354 
355  for(const auto& [type_id, type] : unit_types.types()) {
356  // Only show the weapon special if we find it on a unit that
357  // detailed description should be shown about.
359  continue;
360 
361  for(const attack_type& atk : type.attacks()) {
362  for(const auto& [name, description] : atk.special_tooltips()) {
363  special_description.emplace(name, description);
364 
365  if (!type.hide_help()) {
366  //add a link in the list of units having this special
367  std::string type_name = type.type_name();
368  //check for variations (walking corpse/soulless etc)
369  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
370  std::string ref_id = section_prefix + unit_prefix + type.id();
371  //we put the translated name at the beginning of the hyperlink,
372  //so the automatic alphabetic sorting of std::set can use it
373  std::string link = markup::make_link(type_name, ref_id);
374  special_units[name].insert(link);
375  }
376  }
377  }
378 
379  for(const config& adv : type.modification_advancements()) {
380  for(const config& effect : adv.child_range("effect")) {
381  if(effect["apply_to"] == "new_attack" && effect.has_child("specials")) {
382  for(const auto [key, special] : effect.mandatory_child("specials").all_children_view()) {
383  if(!special["name"].empty()) {
384  special_description.emplace(special["name"].t_str(), special["description"].t_str());
385  if(!type.hide_help()) {
386  //add a link in the list of units having this special
387  std::string type_name = type.type_name();
388  //check for variations (walking corpse/soulless etc)
389  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
390  std::string ref_id = section_prefix + unit_prefix + type.id();
391  //we put the translated name at the beginning of the hyperlink,
392  //so the automatic alphabetic sorting of std::set can use it
393  std::string link = markup::make_link(type_name, ref_id);
394  special_units[special["name"].t_str()].insert(link);
395  }
396  }
397  }
398  } else if(effect["apply_to"] == "attack" && effect.has_child("set_specials")) {
399  for(const auto [key, special] : effect.mandatory_child("set_specials").all_children_view()) {
400  if(!special["name"].empty()) {
401  special_description.emplace(special["name"].t_str(), special["description"].t_str());
402  if(!type.hide_help()) {
403  //add a link in the list of units having this special
404  std::string type_name = type.type_name();
405  //check for variations (walking corpse/soulless etc)
406  const std::string section_prefix = type.show_variations_in_help() ? ".." : "";
407  std::string ref_id = section_prefix + unit_prefix + type.id();
408  //we put the translated name at the beginning of the hyperlink,
409  //so the automatic alphabetic sorting of std::set can use it
410  std::string link = markup::make_link(type_name, ref_id);
411  special_units[special["name"].t_str()].insert(link);
412  }
413  }
414  }
415  }
416  }
417  }
418  }
419 
420  for(const auto& [name, description] : special_description) {
421  // use untranslated name to have universal topic id
422  std::string id = "weaponspecial_" + name.base_str();
423  std::stringstream text;
424  text << description;
425  text << "\n\n" << markup::tag("header", _("Units with this special attack")) << "\n";
426  for(const std::string& type_id : special_units[name]) {
427  text << font::unicode_bullet << " " << type_id << "\n";
428  }
429 
430  topics.emplace_back(name, id, text.str());
431  }
432 
433  if (sort_generated)
434  std::sort(topics.begin(), topics.end(), title_less());
435  return topics;
436 }
437 
438 std::vector<topic> generate_ability_topics(const bool sort_generated)
439 {
440  std::vector<topic> topics;
441 
442  std::map<std::string, const unit_type::ability_metadata*> ability_topic_data;
443  std::map<std::string, std::set<std::string, string_less>> ability_units;
444 
445  const auto parse = [&](const unit_type& type, const unit_type::ability_metadata& ability) {
446  // NOTE: neither ability names nor ability ids are necessarily unique. Creating
447  // topics for either each unique name or each unique id means certain abilities
448  // will be excluded from help. So... the ability topic ref id is a combination
449  // of id and (untranslated) name. It's rather ugly, but it works.
450  const std::string topic_ref = ability.id + ability.name.base_str();
451 
452  ability_topic_data.emplace(topic_ref, &ability);
453 
454  if(!type.hide_help()) {
455  // Add a link in the list of units with this ability
456  // We put the translated name at the beginning of the hyperlink,
457  // so the automatic alphabetic sorting of std::set can use it
458  const std::string link = markup::make_link(type.type_name(), unit_prefix + type.id());
459  ability_units[topic_ref].insert(link);
460  }
461  };
462 
463  // Look through all the unit types. If a unit of that type would have a full
464  // description, add its abilities to the potential topic list. We don't want
465  // to show abilities that the user has not encountered yet.
466  for(const auto& [type_id, type] : unit_types.types()) {
468  continue;
469  }
470 
471  for(const unit_type::ability_metadata& ability : type.abilities_metadata()) {
472  parse(type, ability);
473  }
474 
475  for(const unit_type::ability_metadata& ability : type.adv_abilities_metadata()) {
476  parse(type, ability);
477  }
478  }
479 
480  for(const auto& a : ability_topic_data) {
481  if (a.second->name.empty()) {
482  continue;
483  }
484  std::ostringstream text;
485  text << a.second->description;
486  text << "\n\n" << markup::tag("header", _("Units with this ability")) << "\n";
487 
488  for(const auto& u : ability_units[a.first]) { // first is the topic ref id
489  text << font::unicode_bullet << " " << u << "\n";
490  }
491 
492  topics.emplace_back(a.second->name, ability_prefix + a.first, text.str());
493  }
494 
495  if(sort_generated) {
496  std::sort(topics.begin(), topics.end(), title_less());
497  }
498 
499  return topics;
500 }
501 
502 std::vector<topic> generate_era_topics(const bool sort_generated, const std::string & era_id)
503 {
504  std::vector<topic> topics;
505 
506  auto era = game_config_manager::get()->game_config().find_child("era","id", era_id);
507  if(era && !era["hide_help"].to_bool()) {
508  topics = generate_faction_topics(*era, sort_generated);
509 
510  std::vector<std::string> faction_links;
511  for (const topic & t : topics) {
512  faction_links.push_back(markup::make_link(t.title, t.id));
513  }
514 
515  std::stringstream text;
516  const config::attribute_value& description = era["description"];
517  if (!description.empty()) {
518  text << description.t_str() << "\n";
519  text << "\n";
520  }
521 
522  text << markup::tag("header", _("Factions")) << "\n";
523 
524  std::sort(faction_links.begin(), faction_links.end());
525  for (const std::string &link : faction_links) {
526  text << font::unicode_bullet << " " << link << "\n";
527  }
528 
529  topics.emplace_back(era["name"], ".." + era_prefix + era["id"].str(), text.str());
530  }
531  return topics;
532 }
533 
534 std::vector<topic> generate_faction_topics(const config & era, const bool sort_generated)
535 {
536  std::vector<topic> topics;
537  for (const config &f : era.child_range("multiplayer_side")) {
538  const std::string& id = f["id"];
539  if (id == "Random")
540  continue;
541 
542  std::stringstream text;
543 
544  const config::attribute_value& description = f["description"];
545  if (!description.empty()) {
546  text << description.t_str() << "\n";
547  text << "\n";
548  }
549 
550  const std::vector<std::string> recruit_ids = utils::split(f["recruit"]);
551  std::set<std::string> races;
552  std::set<std::string> alignments;
553 
554  for (const std::string & u_id : recruit_ids) {
555  if (const unit_type * t = unit_types.find(u_id, unit_type::HELP_INDEXED)) {
556  assert(t);
557  const unit_type & type = *t;
558 
559  if (const unit_race *r = unit_types.find_race(type.race_id())) {
560  races.insert(markup::make_link(r->plural_name(), std::string("..") + race_prefix + r->id()));
561  }
562  alignments.insert(markup::make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
563  }
564  }
565 
566  if (!races.empty()) {
567  std::set<std::string>::iterator it = races.begin();
568  text << _("Races: ") << *(it++);
569  while(it != races.end()) {
570  text << ", " << *(it++);
571  }
572  text << "\n\n";
573  }
574 
575  if (!alignments.empty()) {
576  std::set<std::string>::iterator it = alignments.begin();
577  text << _("Alignments: ") << *(it++);
578  while(it != alignments.end()) {
579  text << ", " << *(it++);
580  }
581  text << "\n\n";
582  }
583 
584  text << markup::tag("header", _("Leaders")) << "\n";
585  const std::vector<std::string> leaders =
586  make_unit_links_list( utils::split(f["leader"]), true );
587  for (const std::string &link : leaders) {
588  text << font::unicode_bullet << " " << link << "\n";
589  }
590 
591  text << "\n";
592 
593  text << markup::tag("header", _("Recruits")) << "\n";
594  const std::vector<std::string> recruit_links =
595  make_unit_links_list( recruit_ids, true );
596  for (const std::string &link : recruit_links) {
597  text << font::unicode_bullet << " " << link << "\n";
598  }
599 
600  const std::string name = f["name"];
601  const std::string ref_id = faction_prefix + era["id"].str() + "_" + id;
602  topics.emplace_back(name, ref_id, text.str());
603  }
604  if (sort_generated)
605  std::sort(topics.begin(), topics.end(), title_less());
606  return topics;
607 }
608 
609 std::vector<topic> generate_trait_topics(const bool sort_generated)
610 {
611  // All traits that could be assigned to at least one discovered or HIDDEN_BUT_SHOW_MACROS unit.
612  // This is collected from the [units][trait], [race][traits], and [unit_type][traits] tags. If
613  // there are duplicates with the same id, it takes the first one encountered.
614  std::map<std::string, const config> trait_list;
615 
616  // The global traits that are direct children of a [units] tag
617  for (const config & trait : unit_types.traits()) {
618  trait_list.emplace(trait["id"], trait);
619  }
620 
621  // Search for discovered unit types
622  std::set<std::string> races;
623  for(const auto& i : unit_types.types()) {
624  const unit_type& type = i.second;
626 
627  // Remember which races have been discovered.
628  //
629  // For unit types, unit_type::possible_traits() usually includes racial traits; however it's
630  // possible that all discovered units of a race have ignore_race_traits=yes, and so we still
631  // need to loop over the [race] tags looking for more traits.
632  if(desc_type == FULL_DESCRIPTION) {
633  races.insert(type.race_id());
634  }
635 
636  // Handle [unit_type][trait]s.
637  //
638  // It would be better if we only looked at the traits that are specific to the unit_type,
639  // but that unmerged unit_type_data.traits() isn't available. We're forced to use
640  // possible_traits() instead which returns all of the traits, including the ones that units
641  // with ignore_race_traits=no have inherited from their [race] tag.
642  if (desc_type == FULL_DESCRIPTION || desc_type == HIDDEN_BUT_SHOW_MACROS) {
643  for (const config& trait : type.possible_traits()) {
644  trait_list.emplace(trait["id"], trait);
645  }
646  }
647  }
648 
649  // Race traits, even those that duplicate a global trait (which will be dropped by emplace()).
650  //
651  // For traits, assume we don't discover additional races via the [race]help_taxonomy= links. The
652  // traits themselves don't propagate down those links, so if the trait is interesting w.r.t. the
653  // discovered units then their own race will already include it.
654  for(const auto& race_id : races) {
655  if(const unit_race *r = unit_types.find_race(race_id)) {
656  for(const config & trait : r->additional_traits()) {
657  trait_list.emplace(trait["id"], trait);
658  }
659  }
660  }
661 
662  std::vector<topic> topics;
663  for(auto& a : trait_list) {
664  std::string id = "traits_" + a.first;
665  const config& trait = a.second;
666 
667  std::string name = trait["male_name"].str();
668  if (name.empty()) name = trait["female_name"].str();
669  if (name.empty()) name = trait["name"].str();
670  if (name.empty()) continue; // Hidden trait
671 
672  std::stringstream text;
673  if (!trait["help_text"].empty()) {
674  text << trait["help_text"];
675  } else if (!trait["description"].empty()) {
676  text << trait["description"];
677  } else {
678  text << _("No description available.");
679  }
680  text << "\n\n";
681 
682  topics.emplace_back(name, id, text.str());
683  }
684 
685  if (sort_generated)
686  std::sort(topics.begin(), topics.end(), title_less());
687  return topics;
688 }
689 
690 
691 std::string make_unit_link(const std::string& type_id)
692 {
693  std::string link;
694 
696  if (!type) {
697  PLAIN_LOG << "Unknown unit type : " << type_id;
698  // don't return an hyperlink (no page)
699  // instead show the id (as hint)
700  link = type_id;
701  } else if (!type->hide_help()) {
702  std::string name = type->type_name();
703  std::string ref_id;
705  const std::string section_prefix = type->show_variations_in_help() ? ".." : "";
706  ref_id = section_prefix + unit_prefix + type->id();
707  } else {
708  ref_id = unknown_unit_topic;
709  name += " (?)";
710  }
711  link = markup::make_link(name, ref_id);
712  } // if hide_help then link is an empty string
713 
714  return link;
715 }
716 
717 std::vector<std::string> make_unit_links_list(const std::vector<std::string>& type_id_list, bool ordered)
718 {
719  std::vector<std::string> links_list;
720  for (const std::string &type_id : type_id_list) {
721  std::string unit_link = make_unit_link(type_id);
722  if (!unit_link.empty())
723  links_list.push_back(unit_link);
724  }
725 
726  if (ordered)
727  std::sort(links_list.begin(), links_list.end());
728 
729  return links_list;
730 }
731 
732 void generate_races_sections(const config* help_cfg, section& sec, int level)
733 {
734  std::set<std::string, string_less> races;
735  std::set<std::string, string_less> visible_races;
736 
737  // Calculate which races have been discovered, from the list of discovered unit types.
738  for(const auto& i : unit_types.types()) {
739  const unit_type& type = i.second;
741  if(desc_type == FULL_DESCRIPTION) {
742  races.insert(type.race_id());
743  if(!type.hide_help())
744  visible_races.insert(type.race_id());
745  }
746  }
747 
748  // Propagate visibility up the help_taxonomy tree.
749  std::set<std::string, string_less> last_sweep = visible_races;
750  while(!last_sweep.empty()) {
751  std::set<std::string, string_less> current_sweep;
752  for(const auto& race_id : last_sweep) {
753  if(const unit_race* r = unit_types.find_race(race_id)) {
754  const auto& help_taxonomy = r->help_taxonomy();
755  if(!help_taxonomy.empty() && !visible_races.count(help_taxonomy) && unit_types.find_race(help_taxonomy)) {
756  current_sweep.insert(help_taxonomy);
757  races.insert(help_taxonomy);
758  visible_races.insert(help_taxonomy);
759  }
760  }
761  }
762  last_sweep = std::move(current_sweep);
763  }
764 
765  struct taxonomy_queue_type
766  {
767  std::string parent_id;
768  section content;
769  };
770  std::vector<taxonomy_queue_type> taxonomy_queue;
771 
772  // Add all races without a [race]help_taxonomy= to the documentation section, and queue the others.
773  // This avoids a race condition dependency on the order that races are encountered in help_cfg.
774  for(const auto& race_id : races) {
775  section race_section;
776  config section_cfg;
777 
778  bool hidden = (visible_races.count(race_id) == 0);
779 
780  section_cfg["id"] = hidden_symbol(hidden) + race_prefix + race_id;
781 
782  std::string title;
783  std::string help_taxonomy;
784  if(const unit_race* r = unit_types.find_race(race_id)) {
785  title = r->plural_name();
786  help_taxonomy = r->help_taxonomy();
787  } else {
788  title = _("race^Miscellaneous");
789  // leave help_taxonomy empty
790  }
791  section_cfg["title"] = title;
792 
793  section_cfg["sections_generator"] = "units:" + race_id;
794  section_cfg["generator"] = "units:" + race_id;
795 
796  parse_config_internal(help_cfg, &section_cfg, race_section, level + 1);
797 
798  if(help_taxonomy.empty()) {
799  sec.add_section(race_section);
800  } else {
801  bool parent_hidden = (visible_races.count(help_taxonomy) == 0);
802  auto parent_id = hidden_symbol(parent_hidden) + race_prefix + help_taxonomy;
803  taxonomy_queue.push_back({std::move(parent_id), std::move(race_section)});
804  }
805  }
806 
807  // Each run through this loop handles one level of nesting of [race]help_taxonomy=
808  bool process_queue_again = true;
809  while(process_queue_again && !taxonomy_queue.empty()) {
810  process_queue_again = false;
811  auto to_process = std::exchange(taxonomy_queue, {});
812 
813  for(auto& x : to_process) {
814  auto parent = find_section(sec, x.parent_id);
815  if(parent) {
816  parent->add_section(x.content);
817  process_queue_again = true;
818  } else {
819  taxonomy_queue.push_back(std::move(x));
820  }
821  }
822  }
823 
824  // Fallback to adding the new race at the top level, as if it had help_taxonomy.empty().
825  for(auto& x : taxonomy_queue) {
826  sec.add_section(x.content);
827  }
828 }
829 
830 void generate_era_sections(const config* help_cfg, section & sec, int level)
831 {
832  for(const config& era : game_config_manager::get()->game_config().child_range("era")) {
833  if (era["hide_help"].to_bool()) {
834  continue;
835  }
836 
837  DBG_HP << "Adding help section: " << era["id"].str();
838 
839  section era_section;
840  config section_cfg;
841  section_cfg["id"] = era_prefix + era["id"].str();
842  section_cfg["title"] = era["name"];
843 
844  section_cfg["generator"] = "era:" + era["id"].str();
845 
846  DBG_HP << section_cfg.debug();
847 
848  parse_config_internal(help_cfg, &section_cfg, era_section, level+1);
849  sec.add_section(era_section);
850  }
851 }
852 
853 void generate_terrain_sections(section& sec, int /*level*/)
854 {
855  std::shared_ptr<terrain_type_data> tdata = load_terrain_types_data();
856 
857  if (!tdata) {
858  WRN_HP << "When building terrain help sections, couldn't acquire terrain types data, aborting.";
859  return;
860  }
861 
862  std::map<std::string, section> base_map;
863 
864  const t_translation::ter_list& t_listi = tdata->list();
865 
866  for (const t_translation::terrain_code& t : t_listi) {
867 
868  const terrain_type& info = tdata->get_terrain_info(t);
869 
870  bool hidden = info.hide_help();
871 
872  if (prefs::get().encountered_terrains().find(t)
873  == prefs::get().encountered_terrains().end() && !info.is_overlay())
874  hidden = true;
875 
876  topic terrain_topic{
877  info.editor_name(),
878  hidden_symbol(hidden) + terrain_prefix + info.id(),
879  std::make_shared<terrain_topic_generator>(info)
880  };
881 
882  t_translation::ter_list base_terrains = tdata->underlying_union_terrain(t);
883  if (info.has_default_base()) {
884  for (const auto base : tdata->underlying_union_terrain(info.default_base())) {
885  if (!utils::contains(base_terrains, base)) {
886  base_terrains.emplace_back(base);
887  }
888  }
889  }
890  for (const t_translation::terrain_code& base : base_terrains) {
891 
892  const terrain_type& base_info = tdata->get_terrain_info(base);
893 
894  if (!base_info.is_nonnull() || base_info.hide_help())
895  continue;
896 
897  section& base_section = base_map[base_info.id()];
898 
899  base_section.id = terrain_prefix + base_info.id();
900  base_section.title = base_info.editor_name();
901 
902  if (base_info.id() == info.id())
903  terrain_topic.id = ".." + terrain_prefix + info.id();
904  base_section.topics.push_back(terrain_topic);
905  }
906  }
907 
908  std::vector<section> sorted_sections;
909  for (const auto& pair : base_map) {
910  sorted_sections.push_back(pair.second);
911  }
912 
913  std::sort(sorted_sections.begin(), sorted_sections.end(), section_less());
914 
915  for (const section& s : sorted_sections) {
916  sec.add_section(s);
917  }
918 }
919 
920 void generate_unit_sections(const config* /*help_cfg*/, section& sec, int /*level*/, const bool /*sort_generated*/, const std::string& race)
921 {
922  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types()) {
923  const unit_type &type = i.second;
924 
925  if (type.race_id() != race)
926  continue;
927 
928  if (!type.show_variations_in_help())
929  continue;
930 
931  section base_unit;
932  for (const std::string &variation_id : type.variations()) {
933  // TODO: Do we apply encountered stuff to variations?
934  const unit_type &var_type = type.get_variation(variation_id);
935  const std::string topic_name = var_type.variation_name();
936  const std::string var_ref = hidden_symbol(var_type.hide_help()) + variation_prefix + var_type.id() + "_" + variation_id;
937 
938  base_unit.topics.emplace_back(topic_name, var_ref, std::make_shared<unit_topic_generator>(var_type, variation_id));
939  }
940 
941  const std::string type_name = type.type_name();
942  const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix + type.id();
943 
944  base_unit.id = ref_id;
945  base_unit.title = type_name;
946 
947  sec.add_section(base_unit);
948  }
949 }
950 
951 std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
952 {
953  std::vector<topic> topics;
954  std::set<std::string, string_less> race_units;
955  std::set<std::string, string_less> race_topics;
956  std::set<std::string> alignments;
957 
958  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
959  {
960  const unit_type &type = i.second;
961 
962  if (type.race_id() != race)
963  continue;
964 
966  if (desc_type != FULL_DESCRIPTION)
967  continue;
968 
969  const std::string debug_suffix = (game_config::debug ? " (" + type.id() + ")" : "");
970  const std::string type_name = type.type_name() + (type.id() == type.type_name().str() ? "" : debug_suffix);
971  const std::string real_prefix = type.show_variations_in_help() ? ".." : "";
972  const std::string ref_id = hidden_symbol(type.hide_help()) + real_prefix + unit_prefix + type.id();
973  topics.emplace_back(type_name, ref_id, std::make_shared<unit_topic_generator>(type));
974 
975  if (!type.hide_help()) {
976  // we also record an hyperlink of this unit
977  // in the list used for the race topic
978  std::string link = markup::make_link(type_name, ref_id);
979  race_units.insert(link);
980 
981  alignments.insert(markup::make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
982  }
983  }
984 
985  //generate the hidden race description topic
986  std::string race_id = "..race_"+race;
987  std::string race_name;
988  std::string race_description;
989  std::string race_help_taxonomy;
990  if (const unit_race *r = unit_types.find_race(race)) {
991  race_name = r->plural_name();
992  race_description = r->description();
993  race_help_taxonomy = r->help_taxonomy();
994  // if (description.empty()) description = _("No description Available");
995  for (const config &additional_topic : r->additional_topics())
996  {
997  std::string id = additional_topic["id"];
998  std::string title = additional_topic["title"];
999  std::string text = additional_topic["text"];
1000  //topic additional_topic(title, id, text);
1001  topics.emplace_back(title,id,text);
1002  std::string link = markup::make_link(title, id);
1003  race_topics.insert(link);
1004  }
1005  } else {
1006  race_name = _ ("race^Miscellaneous");
1007  // description = _("Here put the description of the Miscellaneous race");
1008  }
1009 
1010  // Find any other races whose [race]help_taxonomy points to the current race
1011  std::map<std::string, t_string> subgroups;
1012  for (const auto &r : unit_types.races()) {
1013  if (r.second.help_taxonomy() == race) {
1014  if (!r.second.plural_name().empty())
1015  subgroups[r.first] = r.second.plural_name();
1016  else
1017  subgroups[r.first] = r.first;
1018  }
1019  }
1020 
1021  std::stringstream text;
1022 
1023  if (!race_description.empty()) {
1024  text << race_description << "\n\n";
1025  }
1026 
1027  if (!alignments.empty()) {
1028  std::set<std::string>::iterator it = alignments.begin();
1029  text << (alignments.size() > 1 ? _("Alignments: ") : _("Alignment: ")) << *(it++);
1030  while(it != alignments.end()) {
1031  text << ", " << *(it++);
1032  }
1033  text << "\n\n";
1034  }
1035 
1036  if (!race_help_taxonomy.empty()) {
1037  utils::string_map symbols;
1038  symbols["topic_id"] = "..race_"+race_help_taxonomy;
1039  if (const unit_race *r = unit_types.find_race(race_help_taxonomy)) {
1040  symbols["help_taxonomy"] = r->plural_name();
1041  } else {
1042  // Fall back to using showing the race id for the race that we couldn't find.
1043  // Not great, but probably useful if UMC has a broken link.
1044  symbols["help_taxonomy"] = race_help_taxonomy;
1045  }
1046  // TRANSLATORS: this is expected to say "[Dunefolk are] a group of units, all of whom are Humans",
1047  // or "[Quenoth Elves are] a group of units, all of whom are Elves".
1048  text << VGETTEXT("This is a group of units, all of whom are <ref dst='$topic_id'>$help_taxonomy</ref>.", symbols) << "\n\n";
1049  }
1050 
1051  if (!subgroups.empty()) {
1052  if (!race_help_taxonomy.empty()) {
1053  text << markup::tag("header", _("Subgroups of units within this group")) << "\n";
1054  } else {
1055  text << markup::tag("header", _("Groups of units within this race")) << "\n";
1056  }
1057  for (const auto &sg : subgroups) {
1058  text << font::unicode_bullet << " " << markup::make_link(sg.second, "..race_" + sg.first) << "\n";
1059  }
1060  text << "\n";
1061  }
1062 
1063  if (!race_help_taxonomy.empty()) {
1064  text << markup::tag("header", _("Units of this group")) << "\n";
1065  } else {
1066  text << markup::tag("header", _("Units of this race")) << "\n";
1067  }
1068  for (const auto &u : race_units) {
1069  text << font::unicode_bullet << " " << u << "\n";
1070  }
1071 
1072  topics.emplace_back(race_name, race_id, text.str());
1073 
1074  if (sort_generated)
1075  std::sort(topics.begin(), topics.end(), title_less());
1076 
1077  return topics;
1078 }
1079 
1081 {
1082  if (game_config::debug || prefs::get().show_all_units_in_help() ||
1084  return FULL_DESCRIPTION;
1085  }
1086 
1087  const std::set<std::string> &encountered_units = prefs::get().encountered_units();
1088  if (encountered_units.find(type.id()) != encountered_units.end()) {
1089  return FULL_DESCRIPTION;
1090  }
1091 
1092  // See the docs of HIDDEN_BUT_SHOW_MACROS
1093  if (type.id() == "Fog Clearer") {
1094  return HIDDEN_BUT_SHOW_MACROS;
1095  }
1096 
1097  return NO_DESCRIPTION;
1098 }
1099 
1100 std::string generate_contents_links(const std::string& section_name, config const *help_cfg)
1101 {
1102  auto section_cfg = help_cfg->find_child("section", "id", section_name);
1103  if (!section_cfg) {
1104  return std::string();
1105  }
1106 
1107  std::ostringstream res;
1108 
1109  std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);
1110 
1111  // we use an intermediate structure to allow a conditional sorting
1112  typedef std::pair<std::string,std::string> link;
1113  std::vector<link> topics_links;
1114 
1115  // Find all topics in this section.
1116  for(const std::string& topic : topics) {
1117  if (auto topic_cfg = help_cfg->find_child("topic", "id", topic)) {
1118  std::string id = topic_cfg["id"];
1119  if (is_visible_id(id))
1120  topics_links.emplace_back(topic_cfg["title"], id);
1121  }
1122  }
1123 
1124  if (section_cfg["sort_topics"] == "yes") {
1125  std::sort(topics_links.begin(),topics_links.end());
1126  }
1127 
1128  for(const auto& [text, target] : topics_links) {
1129  std::string link = markup::make_link(text, target);
1130  res << font::unicode_bullet << " " << link << "\n";
1131  }
1132 
1133  return res.str();
1134 }
1135 
1136 std::string generate_contents_links(const section &sec)
1137 {
1138  std::stringstream res;
1139 
1140  for (auto &s : sec.sections) {
1141  if (is_visible_id(s.id)) {
1142  std::string link = markup::make_link(s.title, ".."+s.id);
1143  res << font::unicode_bullet << " " << link << "\n";
1144  }
1145  }
1146 
1147  for(const topic& t : sec.topics) {
1148  if (is_visible_id(t.id)) {
1149  std::string link = markup::make_link(t.title, t.id);
1150  res << font::unicode_bullet << " " << link << "\n";
1151  }
1152  }
1153  return res.str();
1154 }
1155 
1156 bool topic::operator==(const topic &t) const
1157 {
1158  return t.id == id;
1159 }
1160 
1161 bool topic::operator<(const topic &t) const
1162 {
1163  return id < t.id;
1164 }
1165 
1166 bool section::operator==(const section &sec) const
1167 {
1168  return sec.id == id;
1169 }
1170 
1171 bool section::operator<(const section &sec) const
1172 {
1173  return id < sec.id;
1174 }
1175 
1177 {
1178  sections.emplace_back(s);
1179 }
1180 
1182 {
1183  topics.clear();
1184  sections.clear();
1185 }
1186 
1187 const topic *find_topic(const section &sec, const std::string &id)
1188 {
1189  topic_list::const_iterator tit =
1190  std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
1191  if (tit != sec.topics.end()) {
1192  return &(*tit);
1193  }
1194  for (const auto &s : sec.sections) {
1195  const auto t = find_topic(s, id);
1196  if (t != nullptr) {
1197  return t;
1198  }
1199  }
1200  return nullptr;
1201 }
1202 
1203 const section *find_section(const section &sec, const std::string &id)
1204 {
1205  const auto sit =
1206  std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
1207  if (sit != sec.sections.end()) {
1208  return &*sit;
1209  }
1210  for (const auto &subsection : sec.sections) {
1211  const auto s = find_section(subsection, id);
1212  if (s != nullptr) {
1213  return s;
1214  }
1215  }
1216  return nullptr;
1217 }
1218 
1219 section *find_section(section &sec, const std::string &id)
1220 {
1221  return const_cast<section *>(find_section(const_cast<const section &>(sec), id));
1222 }
1223 
1224 std::pair<section, section> generate_contents()
1225 try {
1226  const config& help_config = game_config_manager::get()->game_config().child_or_empty("help");
1227 
1228  std::vector<std::string> hidden_sections;
1229  std::vector<std::string> hidden_topics;
1230 
1231  section toplevel_section = parse_config(&help_config);
1232 
1233  for(const config& section : help_config.child_range("section")) {
1234  const std::string id = section["id"];
1235 
1236  // This section is not referenced from the toplevel...
1237  if(find_section(toplevel_section, id) == nullptr) {
1238  // ...nor is it referenced from any other section.
1239  if(!section_is_referenced(id, help_config)) {
1240  hidden_sections.push_back(id);
1241  }
1242  }
1243  }
1244 
1245  for(const config& topic : help_config.child_range("topic")) {
1246  const std::string id = topic["id"];
1247 
1248  if(find_topic(toplevel_section, id) == nullptr) {
1249  if(!topic_is_referenced(id, help_config)) {
1250  hidden_topics.push_back(id);
1251  }
1252  }
1253  }
1254 
1255  // Avoid copying the whole help config if nothing is hidden
1256  if(hidden_sections.empty() && hidden_topics.empty()) {
1257  return {std::move(toplevel_section), section{}};
1258  }
1259 
1260  config hidden_config = help_config;
1261  hidden_config.clear_children("toplevel");
1262 
1263  // Replace the toplevel tag with a new one containing everything not referenced
1264  // by the original. Save these sections and topics so that they can be displayed
1265  // later, but hidden when opening the help browser in the usual manner.
1266  hidden_config.add_child("toplevel", config{
1267  "sections", utils::join(hidden_sections),
1268  "topics", utils::join(hidden_topics)
1269  });
1270 
1271  return {std::move(toplevel_section), parse_config(&hidden_config)};
1272 
1273 } catch(const parse_error& e) {
1274  PLAIN_LOG << "Parse error when parsing help text: '" << e.message << "'";
1275  return {};
1276 }
1277 
1278 // id starting with '.' are hidden
1279 std::string hidden_symbol(bool hidden) {
1280  return (hidden ? "." : "");
1281 }
1282 
1283 bool is_visible_id(const std::string &id) {
1284  return (id.empty() || id[0] != '.');
1285 }
1286 
1287 /**
1288  * Return true if the id is valid for user defined topics and
1289  * sections. Some IDs are special, such as toplevel and may not be
1290  * be defined in the config.
1291  */
1292 bool is_valid_id(const std::string &id) {
1293  if (id == "toplevel") {
1294  return false;
1295  }
1296  if (id.compare(0, unit_prefix.length(), unit_prefix) == 0 || id.compare(hidden_symbol().length(), unit_prefix.length(), unit_prefix) == 0) {
1297  return false;
1298  }
1299  if (id.compare(0, 8, "ability_") == 0) {
1300  return false;
1301  }
1302  if (id.compare(0, 14, "weaponspecial_") == 0) {
1303  return false;
1304  }
1305  if (id == "hidden") {
1306  return false;
1307  }
1308  return true;
1309 }
1310 
1311 /** Load the appropriate terrain types data to use */
1312 std::shared_ptr<terrain_type_data> load_terrain_types_data()
1313 {
1314  if (game_config_manager::get()){
1316  } else {
1317  return {};
1318  }
1319 }
1320 
1321 
1322 } // 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:1615
Various functions that implement attacks and attack calculations.
double t
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:157
config & add_child(std::string_view key)
Definition: config.cpp:435
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:379
child_itors child_range(std::string_view key)
Definition: config.cpp:267
void clear_children(T... keys)
Definition: config.hpp:601
optional_config_impl< config > find_child(std::string_view 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:763
std::string debug() const
Definition: config.cpp:1213
static game_config_manager * get()
const std::shared_ptr< terrain_type_data > & terrain_types() const
const game_config_view & game_config() const
const config & child_or_empty(std::string_view key) const
optional_const_config find_child(std::string_view key, const std::string &name, const std::string &value) const
To be used as a function object to locate sections and topics with a specified ID.
Definition: help_impl.hpp:131
To be used as a function object when sorting section lists on the title.
Definition: help_impl.hpp:153
To be used as a function object when sorting topic lists on the title.
Definition: help_impl.hpp:143
std::shared_ptr< topic_generator > generator_
Definition: help_impl.hpp:79
const config & parsed_text() const
Definition: help_impl.cpp:283
config parsed_text_
Definition: help_impl.hpp:78
std::set< std::string > & encountered_units()
static prefs & get()
bool hide_help() const
For instances created from a [terrain_type] tag, the value in the tag (with default false).
Definition: terrain.hpp:61
bool is_nonnull() const
True if this object represents some sentinel values.
Definition: terrain.hpp:129
const std::string & id() const
Definition: terrain.hpp:52
const t_string & editor_name() const
Definition: terrain.hpp:49
int get_max_liminal_bonus() const
const std::vector< time_of_day > & times() const
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:1259
const race_map & races() const
Definition: types.hpp:406
const unit_race * find_race(const std::string &) const
Definition: types.cpp:1364
const unit_type_map & types() const
Definition: types.hpp:396
config_array_view traits() const
Definition: types.hpp:409
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:145
@ HELP_INDEXED
Definition: types.hpp:77
bool hide_help() const
Definition: types.cpp:581
const t_string & variation_name() const
Definition: types.hpp:178
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
#define DBG_HP
Definition: help_impl.cpp:55
#define WRN_HP
Definition: help_impl.cpp:54
static lg::log_domain log_help("help")
Standard logging facilities (interface).
#define PLAIN_LOG
Definition: log.hpp:296
const std::string unicode_bullet
Definition: constants.cpp:47
Game configuration data as global variables.
Definition: build_info.cpp:61
const bool & debug
Definition: game_config.cpp:95
std::vector< topic > generate_unit_topics(const bool sort_generated, const std::string &race)
Definition: help_impl.cpp:951
std::string hidden_symbol(bool hidden)
Definition: help_impl.cpp:1279
void generate_unit_sections(const config *, section &sec, int, const bool, const std::string &race)
Definition: help_impl.cpp:920
UNIT_DESCRIPTION_TYPE
Definition: help_impl.hpp:199
@ FULL_DESCRIPTION
Definition: help_impl.hpp:200
@ HIDDEN_BUT_SHOW_MACROS
Although the unit itself is hidden, traits reachable via this unit are not hidden.
Definition: help_impl.hpp:211
@ NO_DESCRIPTION
Ignore this unit for documentation purposes.
Definition: help_impl.hpp:202
void generate_races_sections(const config *help_cfg, section &sec, int level)
Definition: help_impl.cpp:732
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:89
std::vector< topic > generate_time_of_day_topics(const bool)
Definition: help_impl.cpp:298
void generate_terrain_sections(section &sec, int)
Definition: help_impl.cpp:853
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:691
const std::string unit_prefix
Definition: help_impl.cpp:63
const std::string variation_prefix
Definition: help_impl.cpp:68
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:107
bool is_visible_id(const std::string &id)
Definition: help_impl.cpp:1283
std::vector< topic > generate_faction_topics(const config &era, const bool sort_generated)
Definition: help_impl.cpp:534
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:246
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:1080
const std::string race_prefix
Definition: help_impl.cpp:65
const std::string ability_prefix
Definition: help_impl.cpp:69
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:717
std::vector< topic > generate_topics(const bool sort_generated, const std::string &generator)
Definition: help_impl.cpp:217
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:1203
static std::string time_of_day_bonus_colored(const int time_of_day_bonus)
Definition: help_impl.cpp:293
const std::string terrain_prefix
Definition: help_impl.cpp:64
std::vector< topic > generate_weapon_special_topics(const bool sort_generated)
Definition: help_impl.cpp:348
std::vector< topic > generate_ability_topics(const bool sort_generated)
Definition: help_impl.cpp:438
const std::string unknown_unit_topic
Definition: help_impl.cpp:62
const int max_section_level
Definition: help_impl.cpp:59
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:71
std::shared_ptr< terrain_type_data > load_terrain_types_data()
Load the appropriate terrain types data to use.
Definition: help_impl.cpp:1312
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:1292
std::string generate_contents_links(const std::string &section_name, config const *help_cfg)
Definition: help_impl.cpp:1100
std::string generate_topic_text(const std::string &generator, const config *help_cfg, const section &sec)
Definition: help_impl.cpp:265
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:1187
section parse_config(const config *cfg)
Parse a help config, return the top level section.
Definition: help_impl.cpp:207
std::vector< topic > generate_era_topics(const bool sort_generated, const std::string &era_id)
Definition: help_impl.cpp:502
std::vector< topic > generate_trait_topics(const bool sort_generated)
Definition: help_impl.cpp:609
const std::string default_show_topic
Definition: help_impl.cpp:61
std::pair< section, section > generate_contents()
Generate the help contents from the configurations given to the manager.
Definition: help_impl.cpp:1224
void generate_era_sections(const config *help_cfg, section &sec, int level)
Definition: help_impl.cpp:830
const std::string era_prefix
Definition: help_impl.cpp:67
const std::string faction_prefix
Definition: help_impl.cpp:66
bool is_scope_active(scope s)
Functions to load and save images from/to disk.
logger & info()
Definition: log.cpp:351
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition: markup.cpp:36
std::string make_link(const std::string &text, const std::string &dst)
Generates a Help markup tag corresponding to a reference or link.
Definition: markup.cpp:30
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
config parse_text(const std::string &text)
Parse a xml style marked up text string.
Definition: markup.cpp:443
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:87
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:141
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
Thrown when the help system fails to parse something.
Definition: help_impl.hpp:182
A section contains topics and sections along with title and ID.
Definition: help_impl.hpp:111
section_list sections
Definition: help_impl.hpp:123
bool operator<(const section &) const
Comparison on the ID.
Definition: help_impl.cpp:1171
void add_section(const section &s)
Allocate memory for and add the section.
Definition: help_impl.cpp:1176
std::string id
Definition: help_impl.hpp:121
std::string title
Definition: help_impl.hpp:121
bool operator==(const section &) const
Two sections are equal if their IDs are equal.
Definition: help_impl.cpp:1166
topic_list topics
Definition: help_impl.hpp:122
A topic contains a title, an id and some text.
Definition: help_impl.hpp:92
bool operator==(const topic &) const
Two topics are equal if their IDs are equal.
Definition: help_impl.cpp:1156
std::string id
Definition: help_impl.hpp:102
bool operator<(const topic &) const
Comparison on the ID.
Definition: help_impl.cpp:1161
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1499
#define e
#define f