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