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