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 = info.union_type();
879 
880  if (info.has_default_base()) {
881  for (const auto& base : tdata->get_terrain_info(info.default_base()).union_type()) {
882  if (!utils::contains(base_terrains, base)) {
883  base_terrains.emplace_back(base);
884  }
885  }
886  }
887  for (const t_translation::terrain_code& base : base_terrains) {
888 
889  const terrain_type& base_info = tdata->get_terrain_info(base);
890 
891  if (!base_info.is_nonnull() || base_info.hide_help())
892  continue;
893 
894  section& base_section = base_map[base_info.id()];
895 
896  base_section.id = terrain_prefix + base_info.id();
897  base_section.title = base_info.editor_name();
898 
899  if (base_info.id() == info.id())
900  terrain_topic.id = ".." + terrain_prefix + info.id();
901  base_section.topics.push_back(terrain_topic);
902  }
903  }
904 
905  std::vector<section> sorted_sections;
906  for (const auto& pair : base_map) {
907  sorted_sections.push_back(pair.second);
908  }
909 
910  std::sort(sorted_sections.begin(), sorted_sections.end(), section_less());
911 
912  for (const section& s : sorted_sections) {
913  sec.add_section(s);
914  }
915 }
916 
917 void generate_unit_sections(const config& /*help_cfg*/, section& sec, int /*level*/, const bool /*sort_generated*/, const std::string& race)
918 {
919  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types()) {
920  const unit_type &type = i.second;
921 
922  if (type.race_id() != race)
923  continue;
924 
925  if (!type.show_variations_in_help())
926  continue;
927 
928  section base_unit;
929  for (const std::string &variation_id : type.variations()) {
930  // TODO: Do we apply encountered stuff to variations?
931  const unit_type &var_type = type.get_variation(variation_id);
932  const std::string topic_name = var_type.variation_name();
933  const std::string var_ref = hidden_symbol(var_type.hide_help()) + variation_prefix + var_type.id() + "_" + variation_id;
934 
935  base_unit.topics.emplace_back(topic_name, var_ref, std::make_shared<unit_topic_generator>(var_type, variation_id));
936  }
937 
938  const std::string type_name = type.type_name();
939  const std::string ref_id = hidden_symbol(type.hide_help()) + unit_prefix + type.id();
940 
941  base_unit.id = ref_id;
942  base_unit.title = type_name;
943 
944  sec.add_section(base_unit);
945  }
946 }
947 
948 std::vector<topic> generate_unit_topics(const bool sort_generated, const std::string& race)
949 {
950  std::vector<topic> topics;
951  std::set<std::string, string_less> race_units;
952  std::set<std::string, string_less> race_topics;
953  std::set<std::string> alignments;
954 
955  for (const unit_type_data::unit_type_map::value_type &i : unit_types.types())
956  {
957  const unit_type &type = i.second;
958 
959  if (type.race_id() != race)
960  continue;
961 
963  if (desc_type != FULL_DESCRIPTION)
964  continue;
965 
966  const std::string debug_suffix = (game_config::debug ? " (" + type.id() + ")" : "");
967  const std::string type_name = type.type_name() + (type.id() == type.type_name().str() ? "" : debug_suffix);
968  const std::string real_prefix = type.show_variations_in_help() ? ".." : "";
969  const std::string ref_id = hidden_symbol(type.hide_help()) + real_prefix + unit_prefix + type.id();
970  topics.emplace_back(type_name, ref_id, std::make_shared<unit_topic_generator>(type));
971 
972  if (!type.hide_help()) {
973  // we also record an hyperlink of this unit
974  // in the list used for the race topic
975  std::string link = markup::make_link(type_name, ref_id);
976  race_units.insert(link);
977 
978  alignments.insert(markup::make_link(type.alignment_description(type.alignment(), type.genders().front()), "time_of_day"));
979  }
980  }
981 
982  //generate the hidden race description topic
983  std::string race_id = "..race_"+race;
984  std::string race_name;
985  std::string race_description;
986  std::string race_help_taxonomy;
987  if (const unit_race *r = unit_types.find_race(race)) {
988  race_name = r->plural_name();
989  race_description = r->description();
990  race_help_taxonomy = r->help_taxonomy();
991  // if (description.empty()) description = _("No description Available");
992  for (const config &additional_topic : r->additional_topics())
993  {
994  std::string id = additional_topic["id"];
995  std::string title = additional_topic["title"];
996  std::string text = additional_topic["text"];
997  //topic additional_topic(title, id, text);
998  topics.emplace_back(title,id,text);
999  std::string link = markup::make_link(title, id);
1000  race_topics.insert(link);
1001  }
1002  } else {
1003  race_name = _ ("race^Miscellaneous");
1004  // description = _("Here put the description of the Miscellaneous race");
1005  }
1006 
1007  // Find any other races whose [race]help_taxonomy points to the current race
1008  std::map<std::string, t_string> subgroups;
1009  for (const auto &r : unit_types.races()) {
1010  if (r.second.help_taxonomy() == race) {
1011  if (!r.second.plural_name().empty())
1012  subgroups[r.first] = r.second.plural_name();
1013  else
1014  subgroups[r.first] = r.first;
1015  }
1016  }
1017 
1018  std::stringstream text;
1019 
1020  if (!race_description.empty()) {
1021  text << race_description << "\n\n";
1022  }
1023 
1024  if (!alignments.empty()) {
1025  std::set<std::string>::iterator it = alignments.begin();
1026  text << (alignments.size() > 1 ? _("Alignments: ") : _("Alignment: ")) << *(it++);
1027  while(it != alignments.end()) {
1028  text << ", " << *(it++);
1029  }
1030  text << "\n\n";
1031  }
1032 
1033  if (!race_help_taxonomy.empty()) {
1034  utils::string_map symbols;
1035  symbols["topic_id"] = "..race_"+race_help_taxonomy;
1036  if (const unit_race *r = unit_types.find_race(race_help_taxonomy)) {
1037  symbols["help_taxonomy"] = r->plural_name();
1038  } else {
1039  // Fall back to using showing the race id for the race that we couldn't find.
1040  // Not great, but probably useful if UMC has a broken link.
1041  symbols["help_taxonomy"] = race_help_taxonomy;
1042  }
1043  // TRANSLATORS: this is expected to say "[Dunefolk are] a group of units, all of whom are Humans",
1044  // or "[Quenoth Elves are] a group of units, all of whom are Elves".
1045  text << VGETTEXT("This is a group of units, all of whom are <ref dst='$topic_id'>$help_taxonomy</ref>.", symbols) << "\n\n";
1046  }
1047 
1048  if (!subgroups.empty()) {
1049  if (!race_help_taxonomy.empty()) {
1050  text << markup::tag("header", _("Subgroups of units within this group")) << "\n";
1051  } else {
1052  text << markup::tag("header", _("Groups of units within this race")) << "\n";
1053  }
1054  for (const auto &sg : subgroups) {
1055  text << font::unicode_bullet << " " << markup::make_link(sg.second, "..race_" + sg.first) << "\n";
1056  }
1057  text << "\n";
1058  }
1059 
1060  if (!race_help_taxonomy.empty()) {
1061  text << markup::tag("header", _("Units of this group")) << "\n";
1062  } else {
1063  text << markup::tag("header", _("Units of this race")) << "\n";
1064  }
1065  for (const auto &u : race_units) {
1066  text << font::unicode_bullet << " " << u << "\n";
1067  }
1068 
1069  topics.emplace_back(race_name, race_id, text.str());
1070 
1071  if (sort_generated)
1072  std::sort(topics.begin(), topics.end(), title_less());
1073 
1074  return topics;
1075 }
1076 
1078 {
1079  if (game_config::debug || prefs::get().show_all_units_in_help() ||
1081  return FULL_DESCRIPTION;
1082  }
1083 
1084  const std::set<std::string> &encountered_units = prefs::get().encountered_units();
1085  if (encountered_units.find(type.id()) != encountered_units.end()) {
1086  return FULL_DESCRIPTION;
1087  }
1088 
1089  // See the docs of HIDDEN_BUT_SHOW_MACROS
1090  if (type.id() == "Fog Clearer") {
1091  return HIDDEN_BUT_SHOW_MACROS;
1092  }
1093 
1094  return NO_DESCRIPTION;
1095 }
1096 
1097 std::string generate_contents_links(const std::string& section_name, const config& help_cfg)
1098 {
1099  auto section_cfg = help_cfg.find_child("section", "id", section_name);
1100  if (!section_cfg) {
1101  return std::string();
1102  }
1103 
1104  std::ostringstream res;
1105 
1106  std::vector<std::string> topics = utils::quoted_split(section_cfg["topics"]);
1107 
1108  // we use an intermediate structure to allow a conditional sorting
1109  typedef std::pair<std::string,std::string> link;
1110  std::vector<link> topics_links;
1111 
1112  // Find all topics in this section.
1113  for(const std::string& topic : topics) {
1114  if (auto topic_cfg = help_cfg.find_child("topic", "id", topic)) {
1115  std::string id = topic_cfg["id"];
1116  if (is_visible_id(id))
1117  topics_links.emplace_back(topic_cfg["title"], id);
1118  }
1119  }
1120 
1121  if (section_cfg["sort_topics"] == "yes") {
1122  std::sort(topics_links.begin(),topics_links.end());
1123  }
1124 
1125  for(const auto& [text, target] : topics_links) {
1126  std::string link = markup::make_link(text, target);
1127  res << font::unicode_bullet << " " << link << "\n";
1128  }
1129 
1130  return res.str();
1131 }
1132 
1133 std::string generate_contents_links(const section &sec)
1134 {
1135  std::stringstream res;
1136 
1137  for (auto &s : sec.sections) {
1138  if (is_visible_id(s.id)) {
1139  std::string link = markup::make_link(s.title, ".."+s.id);
1140  res << font::unicode_bullet << " " << link << "\n";
1141  }
1142  }
1143 
1144  for(const topic& t : sec.topics) {
1145  if (is_visible_id(t.id)) {
1146  std::string link = markup::make_link(t.title, t.id);
1147  res << font::unicode_bullet << " " << link << "\n";
1148  }
1149  }
1150  return res.str();
1151 }
1152 
1153 bool topic::operator==(const topic &t) const
1154 {
1155  return t.id == id;
1156 }
1157 
1158 bool topic::operator<(const topic &t) const
1159 {
1160  return id < t.id;
1161 }
1162 
1163 bool section::operator==(const section &sec) const
1164 {
1165  return sec.id == id;
1166 }
1167 
1168 bool section::operator<(const section &sec) const
1169 {
1170  return id < sec.id;
1171 }
1172 
1174 {
1175  sections.emplace_back(s);
1176 }
1177 
1179 {
1180  sections.emplace_back(std::move(s));
1181 }
1182 
1184 {
1185  topics.clear();
1186  sections.clear();
1187 }
1188 
1189 const topic *find_topic(const section &sec, const std::string &id)
1190 {
1191  topic_list::const_iterator tit =
1192  std::find_if(sec.topics.begin(), sec.topics.end(), has_id(id));
1193  if (tit != sec.topics.end()) {
1194  return &(*tit);
1195  }
1196  for (const auto &s : sec.sections) {
1197  const auto t = find_topic(s, id);
1198  if (t != nullptr) {
1199  return t;
1200  }
1201  }
1202  return nullptr;
1203 }
1204 
1205 const section *find_section(const section &sec, const std::string &id)
1206 {
1207  const auto sit =
1208  std::find_if(sec.sections.begin(), sec.sections.end(), has_id(id));
1209  if (sit != sec.sections.end()) {
1210  return &*sit;
1211  }
1212  for (const auto &subsection : sec.sections) {
1213  const auto s = find_section(subsection, id);
1214  if (s != nullptr) {
1215  return s;
1216  }
1217  }
1218  return nullptr;
1219 }
1220 
1221 section *find_section(section &sec, const std::string &id)
1222 {
1223  return const_cast<section *>(find_section(const_cast<const section &>(sec), id));
1224 }
1225 
1226 std::pair<section, section> generate_contents()
1227 try {
1228  const config& help_config = game_config_manager::get()->game_config().child_or_empty("help");
1229 
1230  std::vector<std::string> hidden_sections;
1231  std::vector<std::string> hidden_topics;
1232 
1233  section toplevel_section = parse_config(help_config);
1234 
1235  for(const config& section : help_config.child_range("section")) {
1236  const std::string id = section["id"];
1237 
1238  // This section is not referenced from the toplevel...
1239  if(find_section(toplevel_section, id) == nullptr) {
1240  // ...nor is it referenced from any other section.
1241  if(!section_is_referenced(id, help_config)) {
1242  hidden_sections.push_back(id);
1243  }
1244  }
1245  }
1246 
1247  for(const config& topic : help_config.child_range("topic")) {
1248  const std::string id = topic["id"];
1249 
1250  if(find_topic(toplevel_section, id) == nullptr) {
1251  if(!topic_is_referenced(id, help_config)) {
1252  hidden_topics.push_back(id);
1253  }
1254  }
1255  }
1256 
1257  // Avoid copying the whole help config if nothing is hidden
1258  if(hidden_sections.empty() && hidden_topics.empty()) {
1259  return {std::move(toplevel_section), section{}};
1260  }
1261 
1262  config hidden_config = help_config;
1263  hidden_config.clear_children("toplevel");
1264 
1265  // Replace the toplevel tag with a new one containing everything not referenced
1266  // by the original. Save these sections and topics so that they can be displayed
1267  // later, but hidden when opening the help browser in the usual manner.
1268  hidden_config.add_child("toplevel", config{
1269  "sections", utils::join(hidden_sections),
1270  "topics", utils::join(hidden_topics)
1271  });
1272 
1273  return {std::move(toplevel_section), parse_config(hidden_config)};
1274 
1275 } catch(const parse_error& e) {
1276  PLAIN_LOG << "Parse error when parsing help text: '" << e.message << "'";
1277  return {};
1278 }
1279 
1280 // id starting with '.' are hidden
1281 std::string hidden_symbol(bool hidden) {
1282  return (hidden ? "." : "");
1283 }
1284 
1285 bool is_visible_id(const std::string &id) {
1286  return (id.empty() || id[0] != '.');
1287 }
1288 
1289 /**
1290  * Return true if the id is valid for user defined topics and
1291  * sections. Some IDs are special, such as toplevel and may not be
1292  * be defined in the config.
1293  */
1294 bool is_valid_id(const std::string &id) {
1295  if (id == "toplevel") {
1296  return false;
1297  }
1298  if (id.compare(0, unit_prefix.length(), unit_prefix) == 0 || id.compare(hidden_symbol().length(), unit_prefix.length(), unit_prefix) == 0) {
1299  return false;
1300  }
1301  if (id.compare(0, 8, "ability_") == 0) {
1302  return false;
1303  }
1304  if (id.compare(0, 14, "weaponspecial_") == 0) {
1305  return false;
1306  }
1307  if (id == "hidden") {
1308  return false;
1309  }
1310  return true;
1311 }
1312 
1313 } // 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:1510
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:139
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:243
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:948
std::string hidden_symbol(bool hidden)
Definition: help_impl.cpp:1281
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:1285
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:1077
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:1205
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:917
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:1097
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:1294
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:1189
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:1226
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:1168
void add_section(const section &s)
Allocate memory for and add the section.
Definition: help_impl.cpp:1173
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:1163
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:1153
std::string id
Definition: help_impl.hpp:101
bool operator<(const topic &) const
Comparison on the ID.
Definition: help_impl.cpp:1158
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