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