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