The Battle for Wesnoth  1.19.21+dev
reports.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "reports.hpp"
17 
18 #include "actions/attack.hpp"
19 #include "attack_prediction.hpp"
20 #include "color.hpp"
21 #include "desktop/battery_info.hpp"
22 #include "font/pango/escape.hpp"
23 #include "font/standard_colors.hpp"
24 #include "formatter.hpp"
25 #include "formula/string_utils.hpp"
26 #include "gettext.hpp"
27 #include "help/help.hpp"
28 #include "language.hpp"
29 #include "map/map.hpp"
30 #include "mouse_events.hpp"
31 #include "pathfind/pathfind.hpp"
32 #include "picture.hpp"
34 #include "serialization/chrono.hpp"
35 #include "serialization/markup.hpp"
36 #include "team.hpp"
37 #include "terrain/movement.hpp"
38 #include "tod_manager.hpp"
39 #include "units/unit.hpp"
40 #include "units/helper.hpp"
41 #include "units/types.hpp"
43 #include "whiteboard/manager.hpp"
44 
45 #include <boost/format.hpp>
46 #include <iomanip>
47 #include <utility>
48 
49 #ifdef __cpp_lib_format
50 #include <format>
51 #endif
52 
53 static void add_text(config &report, const std::string &text,
54  const std::string &tooltip, const std::string &help = "")
55 {
56  config &element = report.add_child("element");
57  element["text"] = text;
58  if (!tooltip.empty()) element["tooltip"] = tooltip;
59  if (!help.empty()) element["help"] = help;
60 }
61 
62 static void add_image(config &report, const std::string &image,
63  const std::string &tooltip, const std::string &help = "")
64 {
65  config &element = report.add_child("element");
66  element["image"] = image;
67  if (!tooltip.empty()) element["tooltip"] = tooltip;
68  if (!help.empty()) element["help"] = help;
69 }
70 
71 static config text_report(const std::string &text,
72  const std::string &tooltip = "", const std::string &help = "")
73 {
74  config r;
75  add_text(r, text, tooltip, help);
76  return r;
77 }
78 
79 static config image_report(const std::string &image,
80  const std::string &tooltip = "", const std::string &help = "")
81 {
82  config r;
84  return r;
85 }
86 
87 using markup::span_color;
88 
89 static void add_status(config &r,
90  const std::string& path, char const *desc1, char const *desc2)
91 {
92  std::ostringstream s;
93  s << translation::gettext(desc1) << " " << translation::gettext(desc2);
94  add_image(r, path, s.str());
95 }
96 
97 static std::string flush(std::ostringstream &s)
98 {
99  std::string r(s.str());
100  s.str(std::string());
101  return r;
102 }
103 
105 {
106  const team &viewing_team = rc.screen().viewing_team();
107  if (viewing_team.shrouded(hex)) {
108  // Don't show time on shrouded tiles.
109  return rc.tod().get_time_of_day();
110  } else if (viewing_team.fogged(hex)) {
111  // Don't show illuminated time on fogged tiles.
112  return rc.tod().get_time_of_day(hex);
113  } else {
114  return rc.tod().get_illuminated_time_of_day(rc.units(), rc.map(), hex);
115  }
116 }
117 
118 typedef std::map<std::string, reports::generator_function> static_report_generators;
120 
122 {
124  {
125  static_generators.insert(static_report_generators::value_type(name, g));
126  }
127 };
128 
129 #define REPORT_GENERATOR(n, cn) \
130  static config report_##n(const reports::context& cn); \
131  static report_generator_helper reg_gen_##n(#n, &report_##n); \
132  static config report_##n(const reports::context& cn)
133 
134 static const unit *get_visible_unit(const reports::context& rc)
135 {
136  return rc.dc().get_visible_unit(rc.screen().displayed_unit_hex(),
137  rc.screen().viewing_team(),
138  rc.screen().show_everything());
139 }
140 
141 static const unit *get_selected_unit(const reports::context& rc)
142 {
143  return rc.dc().get_visible_unit(rc.screen().selected_hex(),
144  rc.screen().viewing_team(),
145  rc.screen().show_everything());
146 }
147 
149 {
151  rc.screen().viewing_team(),
152  rc.screen().show_everything());
153 }
154 
155 static config gray_inactive(const reports::context& rc, const std::string &str, const std::string& tooltip = "")
156 {
157  if ( rc.screen().viewing_team_is_playing() )
158  return text_report(str, tooltip);
159 
161 }
162 
163 static config unit_name(const unit *u)
164 {
165  if (!u) {
166  return config();
167  }
168 
169  /*
170  * The name needs to be escaped, it might be set by the user and using
171  * markup. Also names often contain a forbidden single quote.
172  */
173  const std::string& name = font::escape_text(u->name().str());
174  std::ostringstream str, tooltip;
175  str << markup::bold(name);
176  tooltip << _("Name:") << " " << markup::bold(name);
177  return text_report(str.str(), tooltip.str());
178 }
179 
181 {
182  const unit *u = get_visible_unit(rc);
183  return unit_name(u);
184 }
185 REPORT_GENERATOR(selected_unit_name, rc)
186 {
187  const unit *u = get_selected_unit(rc);
188  return unit_name(u);
189 }
190 
191 static config unit_type(const unit* u)
192 {
193  if(!u) return config();
194 
195  std::ostringstream tooltip;
196  tooltip << _("Type:") << " "
197  << markup::bold(font::escape_text(u->type_name())) << "\n"
199  if(const auto& notes = u->unit_special_notes(); !notes.empty()) {
200  tooltip << "\n\n" << _("Special Notes:") << '\n';
201  for(const auto& note : notes) {
202  tooltip << font::unicode_bullet << " " << note << '\n';
203  }
204  }
205 
206  return text_report(u->type_name(), tooltip.str(), help::get_unit_type_help_id(u->type()));
207 }
209 {
210  const unit *u = get_visible_unit(rc);
211  return unit_type(u);
212 }
213 REPORT_GENERATOR(selected_unit_type, rc)
214 {
215  const unit *u = get_selected_unit(rc);
216  return unit_type(u);
217 }
218 
219 static config unit_race(const unit* u)
220 {
221  if (!u) return config();
222  std::ostringstream str, tooltip;
223  str << u->race()->name(u->gender());
224  tooltip << _("Race:") << " " << markup::bold(u->race()->name(u->gender()));
225  return text_report(str.str(), tooltip.str(), "..race_" + u->race()->id());
226 }
228 {
229  const unit *u = get_visible_unit(rc);
230  return unit_race(u);
231 }
232 REPORT_GENERATOR(selected_unit_race, rc)
233 {
234  const unit *u = get_selected_unit(rc);
235  return unit_race(u);
236 }
237 
238 static std::string side_tooltip(const team& team)
239 {
240  if(team.side_name().empty())
241  return "";
242 
243  return VGETTEXT("Side: <b>$side_name</b> ($color_name)",
244  {{"side_name", team.side_name()},
245  {"color_name", team::get_side_color_name_for_UI(team.side()) }});
246 }
247 
248 static config unit_side(const reports::context& rc, const unit* u)
249 {
250  if (!u) return config();
251 
252  config report;
253  const team &u_team = rc.dc().get_team(u->side());
254  std::string flag_icon = u_team.flag_icon();
255  std::string old_rgb = game_config::flag_rgb;
256  std::string new_rgb = u_team.color();
257  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
258  if (flag_icon.empty())
260 
261  std::stringstream text;
262  text << " " << u->side();
263 
264  const std::string& tooltip = side_tooltip(u_team);
265  add_image(report, flag_icon + mods, tooltip, "");
266  add_text(report, text.str(), tooltip, "");
267  return report;
268 }
270 {
271  const unit *u = get_visible_unit(rc);
272  return unit_side(rc,u);
273 }
274 REPORT_GENERATOR(selected_unit_side, rc)
275 {
276  const unit *u = get_selected_unit(rc);
277  return unit_side(rc, u);
278 }
279 
280 static config unit_level(const unit* u)
281 {
282  if (!u) return config();
283  return text_report(std::to_string(u->level()), unit_helper::unit_level_tooltip(*u));
284 }
286 {
287  const unit *u = get_visible_unit(rc);
288  return unit_level(u);
289 }
290 REPORT_GENERATOR(selected_unit_level, rc)
291 {
292  const unit *u = get_selected_unit(rc);
293  return unit_level(u);
294 }
295 
296 REPORT_GENERATOR(unit_amla, rc)
297 {
298  const unit *u = get_visible_unit(rc);
299  if (!u) return config();
300  config res;
301  typedef std::pair<std::string, std::string> pair_string;
302  for (const pair_string &ps : u->amla_icons()) {
303  add_image(res, ps.first, ps.second);
304  }
305  return res;
306 }
307 
308 static config unit_traits(const unit* u)
309 {
310  if (!u) return config();
311  config res;
312  const std::vector<t_string> &traits = u->trait_names();
313  const std::vector<t_string> &descriptions = u->trait_descriptions();
314  const std::vector<std::string> &trait_ids = u->trait_nonhidden_ids();
315  unsigned nb = traits.size();
316  for (unsigned i = 0; i < nb; ++i)
317  {
318  std::ostringstream str, tooltip;
319  str << traits[i];
320  if (i != nb - 1 ) str << ", ";
321  tooltip << _("Trait:") << " " << markup::bold(traits[i]) << "\n"
322  << descriptions[i];
323  add_text(res, str.str(), tooltip.str(), "traits_" + trait_ids[i]);
324  }
325  return res;
326 }
328 {
329  const unit *u = get_visible_unit(rc);
330  return unit_traits(u);
331 }
332 REPORT_GENERATOR(selected_unit_traits, rc)
333 {
334  const unit *u = get_selected_unit(rc);
335  return unit_traits(u);
336 }
337 
338 static config unit_status(const reports::context& rc, const unit* u)
339 {
340  if (!u) return config();
341  config res;
342  map_location displayed_unit_hex = rc.screen().displayed_unit_hex();
343  if (rc.map().on_board(displayed_unit_hex) && u->invisible(displayed_unit_hex)) {
344  add_status(res, "misc/invisible.png", N_("invisible:"),
345  N_("This unit is invisible. It cannot be seen or attacked by enemy units."));
346  }
347  if (u->get_state(unit::STATE_SLOWED)) {
348  add_status(res, "misc/slowed.png", N_("slowed:"),
349  N_("This unit has been slowed. It will only deal half its normal damage when attacking and its movement cost is doubled."));
350  }
352  add_status(res, "misc/poisoned.png", N_("poisoned:"),
353  N_("This unit is poisoned. It will lose 8 HP every turn until it can seek a cure to the poison in a village or from a friendly unit with the ‘cures’ ability.\n\nUnits cannot be killed by poison alone. The poison will not reduce it below 1 HP."));
354  }
356  add_status(res, "misc/petrified.png", N_("petrified:"),
357  N_("This unit has been petrified. It may not move or attack."));
358  }
360  add_status(res, "misc/unhealable.png", N_("unhealable:"),
361  N_("This unit is unhealable. It cannot be healed by healers or villages and doesn’t benefit from resting."));
362  }
364  add_status(res, "misc/invulnerable.png", N_("invulnerable:"),
365  N_("This unit is invulnerable. It cannot be harmed by any attack."));
366  }
367  return res;
368 }
370 {
371  const unit *u = get_visible_unit(rc);
372  return unit_status(rc,u);
373 }
374 REPORT_GENERATOR(selected_unit_status, rc)
375 {
376  const unit *u = get_selected_unit(rc);
377  return unit_status(rc, u);
378 }
379 
380 static config unit_alignment(const reports::context& rc, const unit* u, const map_location& hex)
381 {
382  if (!u) return config();
383  std::ostringstream str, tooltip;
384  const std::string align = unit_type::alignment_description(u->alignment(), u->gender());
385  const std::string align_id = unit_alignments::get_string(u->alignment());
386  const time_of_day effective_tod = get_visible_time_of_day_at(rc, hex);
387  int cm = combat_modifier(effective_tod, u->alignment(), u->is_fearless());
388 
389  color_t color = font::weapon_color;
390  if (cm != 0)
391  color = (cm > 0) ? font::good_dmg_color : font::bad_dmg_color;
392 
393  str << align << " (" << span_color(color, utils::signed_percent(cm)) << ")";
394 
395  tooltip << _("Alignment:") << " " << markup::bold(align) << "\n"
396  << string_table[align_id + "_description"];
397 
398  return text_report(str.str(), tooltip.str(), "time_of_day");
399 }
401 {
402  const unit *u = get_visible_unit(rc);
403  const map_location& mouseover_hex = rc.screen().mouseover_hex();
404  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
405  const map_location& hex = mouseover_hex.valid() ? mouseover_hex : displayed_unit_hex;
406  return unit_alignment(rc, u, hex);
407 }
408 REPORT_GENERATOR(selected_unit_alignment, rc)
409 {
410  const unit *u = get_selected_unit(rc);
411  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
412  const map_location& hex_to_show_alignment_at =
413  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
414  return unit_alignment(rc, u, hex_to_show_alignment_at);
415 }
416 
418 {
419  if (!u) return config();
420  config res;
421 
422  boost::dynamic_bitset<> active;
423  const auto abilities = u->ability_tooltips(active, loc);
424 
425  const std::size_t abilities_size = abilities.size();
426  for(std::size_t i = 0; i != abilities_size; ++i) {
427  // Aliases for readability:
428  const auto& [display_name, description, help_id] = abilities[i];
429 
430  std::ostringstream str, tooltip;
431 
432  if(active[i]) {
433  str << display_name;
434  } else {
435  str << span_color(font::INACTIVE_COLOR, display_name);
436  }
437 
438  if(i + 1 != abilities_size) {
439  str << ", ";
440  }
441 
442  tooltip << _("Ability:") << " " << markup::bold(display_name);
443  if(!active[i]) {
444  tooltip << markup::italic(_(" (inactive)"));
445  }
446 
447  tooltip << '\n' << description;
448 
449  add_text(res, str.str(), tooltip.str(), "ability_" + help_id);
450  }
451 
452  return res;
453 }
455 {
456  const unit *u = get_visible_unit(rc);
457  const team &viewing_team = rc.screen().viewing_team();
458  const map_location& mouseover_hex = rc.screen().mouseover_hex();
459  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
460  const map_location& hex = (mouseover_hex.valid() && !viewing_team.shrouded(mouseover_hex)) ? mouseover_hex : displayed_unit_hex;
461 
462  return unit_abilities_report(u, hex);
463 }
464 REPORT_GENERATOR(selected_unit_abilities, rc)
465 {
466  const unit *u = get_selected_unit(rc);
467 
468  const map_location& mouseover_hex = rc.screen().mouseover_hex();
469  const unit *visible_unit = get_visible_unit(rc);
470  const team &viewing_team = rc.screen().viewing_team();
471 
472  if (visible_unit && u && visible_unit->id() != u->id() && mouseover_hex.valid() && !viewing_team.shrouded(mouseover_hex))
473  return unit_abilities_report(u, mouseover_hex);
474  else
475  return unit_abilities_report(u, u->get_location());
476 }
477 
478 
479 static config unit_hp(const reports::context& rc, const unit* u)
480 {
481  if (!u) return config();
482  std::ostringstream str, tooltip;
483  str << span_color(u->hp_color(), u->hitpoints(), '/', u->max_hitpoints());
484 
485  std::vector<std::string> resistances_table;
486 
487  bool att_def_diff = false;
488  map_location displayed_unit_hex = rc.screen().displayed_unit_hex();
489  for (const utils::string_map_res::value_type &resist : u->get_base_resistances())
490  {
491  std::ostringstream line;
492  line << translation::gettext(resist.first.c_str()) << ": ";
493  // Some units have different resistances when attacking or defending.
494  int res_att = 100 - u->resistance_against(resist.first, true, displayed_unit_hex);
495  int res_def = 100 - u->resistance_against(resist.first, false, displayed_unit_hex);
496  const std::string def_color = unit_helper::resistance_color(res_def);
497  if (res_att == res_def) {
498  line << span_color(def_color, utils::signed_percent(res_def)) << '\n';
499  } else {
500  const std::string att_color = unit_helper::resistance_color(res_att);
501  line << span_color(att_color, utils::signed_percent(res_att)) << "/";
502  line << span_color(def_color, utils::signed_percent(res_def)) << '\n';
503  att_def_diff = true;
504  }
505  resistances_table.push_back(line.str());
506  }
507 
508  tooltip << _("Resistances:") << " ";
509  if (att_def_diff)
510  tooltip << _("(Att / Def)");
511  tooltip << '\n';
512  for (const std::string &line : resistances_table) {
513  tooltip << line;
514  }
515  return text_report(str.str(), tooltip.str());
516 }
518 {
519  const unit *u = get_visible_unit(rc);
520  return unit_hp(rc, u);
521 }
522 REPORT_GENERATOR(selected_unit_hp, rc)
523 {
524  const unit *u = get_selected_unit(rc);
525  return unit_hp(rc, u);
526 }
527 
528 static config unit_xp(const unit* u)
529 {
530  if (!u) return config();
531  std::ostringstream str, tooltip;
532  if(u->can_advance()) {
533  str << span_color(u->xp_color(), u->experience(), '/', u->max_experience());
534  } else {
536  }
537 
539  tooltip << _("Experience Modifier:") << " " << exp_mod << '%';
540  return text_report(str.str(), tooltip.str());
541 }
543 {
544  const unit *u = get_visible_unit(rc);
545  return unit_xp(u);
546 }
547 REPORT_GENERATOR(selected_unit_xp, rc)
548 {
549  const unit *u = get_selected_unit(rc);
550  return unit_xp(u);
551 }
552 
554 {
555  if (!u) return config();
556  config res;
557  for (const auto& ps : u->advancement_icons()) {
558  add_image(res, ps.first, ps.second);
559  }
560  return res;
561 }
563 {
564  const unit *u = get_visible_unit(rc);
565  return unit_advancement_options(u);
566 }
567 REPORT_GENERATOR(selected_unit_advancement_options, rc)
568 {
569  const unit *u = get_selected_unit(rc);
570  return unit_advancement_options(u);
571 }
572 
573 static config unit_defense(const reports::context& rc, const unit* u, const map_location& displayed_unit_hex)
574 {
575  if(!u) {
576  return config();
577  }
578 
579  std::ostringstream str, tooltip;
580  const gamemap &map = rc.map();
581  if(!rc.map().on_board(displayed_unit_hex)) {
582  return config();
583  }
584 
585  const terrain_type& terrain = map.get_terrain_info(displayed_unit_hex);
586  int def = 100 - u->defense_modifier(terrain.number(), displayed_unit_hex);
587  color_t color = game_config::red_to_green(def);
588  str << span_color(color, def, '%');
589  tooltip << _("Terrain:") << " " << markup::bold(terrain.description()) << "\n";
590 
591  const t_translation::ter_list& underlyings = terrain.def_type();
592  if (underlyings.size() != 1 || underlyings.front() != terrain.number())
593  {
594  bool revert = false;
595  for (const t_translation::terrain_code &t : underlyings)
596  {
597  if (t == t_translation::MINUS) {
598  revert = true;
599  } else if (t == t_translation::PLUS) {
600  revert = false;
601  } else {
602  int t_def = 100 - u->defense_modifier(t);
603  color_t t_color = game_config::red_to_green(t_def);
604  tooltip << '\t' << map.get_terrain_info(t).description() << ": "
605  << span_color(t_color, t_def, '%')
606  << (revert ? _("maximum^max.") : _("minimum^min.")) << '\n';
607  }
608  }
609  }
610 
611  tooltip << markup::bold(_("Defense:")) << " " << span_color(color, def, '%');
612  const std::string has_variations_prefix = (u->type().show_variations_in_help() ? ".." : "");
613  return text_report(str.str(), tooltip.str(), has_variations_prefix + "unit_" + u->type_id());
614 }
616 {
617  const unit *u = get_visible_unit(rc);
618  const team &viewing_team = rc.screen().viewing_team();
619  const map_location& mouseover_hex = rc.screen().mouseover_hex();
620  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
621  const map_location& hex = (mouseover_hex.valid() && !viewing_team.shrouded(mouseover_hex)) ? mouseover_hex : displayed_unit_hex;
622  return unit_defense(rc, u, hex);
623 }
624 REPORT_GENERATOR(selected_unit_defense, rc)
625 {
626  const unit *u = get_selected_unit(rc);
627  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
628  if(attack_indicator_src.valid())
629  return unit_defense(rc, u, attack_indicator_src);
630 
631  const map_location& mouseover_hex = rc.screen().mouseover_hex();
632  const unit *visible_unit = get_visible_unit(rc);
633  if(visible_unit && u && visible_unit->id() != u->id() && mouseover_hex.valid())
634  return unit_defense(rc, u, mouseover_hex);
635  else
636  return unit_defense(rc, u, u->get_location());
637 }
638 
639 static config unit_vision(const unit* u)
640 {
641  if (!u) return config();
642 
643  std::ostringstream str, tooltip;
644  if (u->vision() != u->total_movement()) {
645  // TRANSLATORS: single-letter abbreviation of "vision", fits in a small space on the sidebar
646  str << _("vision^V:") << ' ' << u->vision();
647  tooltip << _("vision:") << ' ' << u->vision() << '\n';
648  }
649  return text_report(str.str(), tooltip.str());
650 }
652 {
653  const unit* u = get_visible_unit(rc);
654  return unit_vision(u);
655 }
656 REPORT_GENERATOR(selected_unit_vision, rc)
657 {
658  const unit* u = get_selected_unit(rc);
659  return unit_vision(u);
660 }
661 
662 static config unit_jamming(const unit* u)
663 {
664  if(!u) return config();
665 
666  std::ostringstream str, tooltip;
667  if(u->jamming() != 0) {
668  // TRANSLATORS: single-letter abbreviation of "jamming", fits in a small space on the sidebar
669  str << _("jamming^J:") << ' ' << u->jamming();
670  tooltip << _("jamming:") << ' ' << u->jamming() << '\n';
671  }
672  return text_report(str.str(), tooltip.str());
673 }
675 {
676  const unit* u = get_visible_unit(rc);
677  return unit_jamming(u);
678 }
679 
680 static config unit_moves(const reports::context& rc, const unit* u, bool is_visible_unit)
681 {
682  if (!u) return config();
683  std::ostringstream str, tooltip;
684  double movement_frac = 1.0;
685 
686  std::set<terrain_movement> terrain_moves;
687 
688  if (u->side() == rc.screen().playing_team().side()) {
689  movement_frac = static_cast<double>(u->movement_left()) / std::max<int>(1, u->total_movement());
690  if (movement_frac > 1.0)
691  movement_frac = 1.0;
692  }
693 
694  tooltip << _("Movement Costs:") << "\n";
695  for (t_translation::terrain_code terrain : prefs::get().encountered_terrains()) {
697  continue;
698 
699  const terrain_type& info = rc.map().get_terrain_info(terrain);
700 
701  if (info.union_type().size() == 1 && info.union_type()[0] == info.number() && info.is_nonnull()) {
702  terrain_moves.emplace(info.name(), u->movement_cost(terrain));
703  }
704  }
705 
706  for (const terrain_movement& tm : terrain_moves) {
707  tooltip << tm.name << ": ";
708 
709  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
710  const bool cannot_move = tm.moves > u->total_movement(); // cannot move in this terrain
711  double movement_red_to_green = 100.0 - 25.0 * tm.moves;
712 
713  std::stringstream temp_str;
714  // A 5 MP margin; if the movement costs go above
715  // the unit's max moves + 5, we replace it with dashes.
716  if (cannot_move && (tm.moves > u->total_movement() + 5)) {
717  temp_str << font::unicode_figure_dash;
718  } else if (cannot_move) {
719  temp_str << "(" << tm.moves << ")";
720  } else {
721  temp_str << tm.moves;
722  }
723  if (tm.moves != 0) {
724  const int movement_hexes_per_turn = u->total_movement() / tm.moves;
725  temp_str << " ";
726  for (int i = 0; i < movement_hexes_per_turn; ++i) {
727  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
728  temp_str << "\u2b23\u200b";
729  }
730  }
731 
732  // passing true to select the less saturated red-to-green scale
733  color_t color = game_config::red_to_green(movement_red_to_green, true);
734  tooltip << span_color(color, temp_str.str()) << '\n';
735 
736  }
737 
738  int grey = 128 + static_cast<int>((255 - 128) * movement_frac);
739  color_t c = color_t(grey, grey, grey);
740  int numerator = u->movement_left();
741  if(is_visible_unit) {
743  if(route.steps.size() > 0) {
744  numerator -= route.move_cost;
745  if(numerator < 0) {
746  // Multi-turn move
747  // TODO: what to show in this case?
748  numerator = 0;
749  }
750 
751  // If the route captures a village, assume that uses up all remaining MP.
752  const auto& end = route.marks.find(route.steps.back());
753  if(end != route.marks.end() && end->second.capture) {
754  numerator = 0;
755  }
756  } else {
757  // TODO: if the mouseover hex is unreachable (for example, a deep water hex
758  // and the current unit is a land unit), what to show?
759  }
760  }
761  str << span_color(c, numerator, '/', u->total_movement());
762  return text_report(str.str(), tooltip.str());
763 }
765 {
766  const unit *u = get_visible_unit(rc);
767  return unit_moves(rc, u, true);
768 }
769 REPORT_GENERATOR(selected_unit_moves, rc)
770 {
771  const unit *u = get_selected_unit(rc);
772  return unit_moves(rc, u, false);
773 }
774 
775 /**
776  * Maps resistance <= -60 (resistance value <= -60%) to intense red.
777  * Maps resistance >= 60 (resistance value >= 60%) to intense green.
778  * Intermediate values are affinely mapped to the red-to-green scale,
779  * with 0 (0%) being mapped to yellow.
780  * Compare unit_helper::resistance_color().
781  */
782 static inline const color_t attack_info_percent_color(int resistance)
783 {
784  // Passing false to select the more saturated red-to-green scale.
785  return game_config::red_to_green(50.0 + resistance * 5.0 / 6.0, false);
786 }
787 
788 static int attack_info(const reports::context& rc, const attack_type &at, config &res, const unit &u, const map_location &hex, const unit* sec_u = nullptr, const const_attack_ptr& sec_u_weapon = nullptr)
789 {
790  std::ostringstream str, tooltip;
791  int damage = 0;
792  const bool attacking = (u.side() == rc.screen().playing_team().side());
793 
794  struct string_with_tooltip {
795  std::string str;
796  std::string tooltip;
797  };
798 
799  {
800  auto ctx = specials_context_t::make({ u.shared_from_this(), hex, at.shared_from_this() }, { }, attacking);
801  int base_damage = at.damage();
802  double specials_damage = at.modified_damage();
803  int damage_multiplier = 100;
804  const_attack_ptr weapon = at.shared_from_this();
805  unit_alignments::type attack_alignment = weapon->alignment().value_or(u.alignment());
806  int tod_bonus = combat_modifier(get_visible_time_of_day_at(rc, hex), attack_alignment, u.is_fearless());
807  damage_multiplier += tod_bonus;
808  int leader_bonus = under_leadership(u, ctx);
809  if (leader_bonus != 0)
810  damage_multiplier += leader_bonus;
811 
813  int damage_divisor = slowed ? 20000 : 10000;
814  // Assume no specific resistance (i.e. multiply by 100).
815  damage = round_damage(specials_damage, damage_multiplier * 100, damage_divisor);
816 
817  // Hit points are used to calculate swarm, so they need to be bounded.
818  unsigned max_hp = u.max_hitpoints();
819  unsigned cur_hp = std::min<unsigned>(std::max(0, u.hitpoints()), max_hp);
820 
821  unsigned base_attacks = at.num_attacks();
822  unsigned min_attacks, max_attacks;
823  at.modified_attacks(min_attacks, max_attacks);
824  unsigned num_attacks = swarm_blows(min_attacks, max_attacks, cur_hp, max_hp);
825 
826  color_t dmg_color = font::weapon_color;
827  if (damage > std::round(specials_damage)) {
828  dmg_color = font::good_dmg_color;
829  } else if (damage < std::round(specials_damage)) {
830  dmg_color = font::bad_dmg_color;
831  }
832 
833  str << span_color(dmg_color, " ", damage)
834  << span_color(font::weapon_color, font::weapon_numbers_sep, num_attacks, ' ', at.name())
835  << "\n";
836  tooltip << _("Weapon:") << " " << markup::bold(at.name()) << "\n"
837  << _("Damage:") << " " << markup::bold(damage) << "\n";
838 
839  if ( tod_bonus || leader_bonus || slowed || specials_damage != base_damage )
840  {
841  tooltip << '\t' << _("Base damage:") << " " << base_damage << '\n';
842  if ( specials_damage != base_damage ) {
843  tooltip << '\t' << _("With specials:") << " " << specials_damage << '\n';
844  }
845  if (tod_bonus) {
846  tooltip << '\t' << _("Time of day:") << " "
847  << utils::signed_percent(tod_bonus) << '\n';
848  }
849  if (leader_bonus) {
850  tooltip << '\t' << _("Leadership:") << " "
851  << utils::signed_percent(leader_bonus) << '\n';
852  }
853  if (slowed) {
854  tooltip << '\t' << _("Slowed:") << " " << "/ 2" << '\n';
855  }
856  }
857 
858  tooltip << _("Attacks:") << " " << markup::bold(num_attacks) << "\n";
859  if ( max_attacks != min_attacks && cur_hp != max_hp ) {
860  if ( max_attacks < min_attacks ) {
861  // "Reverse swarm"
862  tooltip << '\t' << _("Max swarm bonus:") << " " << (min_attacks-max_attacks) << '\n';
863  tooltip << '\t' << _("Swarm:") << " " << "* "<< (100 - cur_hp*100/max_hp) << "%\n";
864  tooltip << '\t' << _("Base attacks:") << " " << '+' << base_attacks << '\n';
865  // The specials line will not necessarily match up with how the
866  // specials are calculated, but for an unusual case, simple brevity
867  // trumps complexities.
868  if ( max_attacks != base_attacks ) {
869  int attack_diff = static_cast<int>(max_attacks) - static_cast<int>(base_attacks);
870  tooltip << '\t' << _("Specials:") << " " << utils::signed_value(attack_diff) << '\n';
871  }
872  }
873  else {
874  // Regular swarm
875  tooltip << '\t' << _("Base attacks:") << " " << base_attacks << '\n';
876  if ( max_attacks != base_attacks ) {
877  tooltip << '\t' << _("With specials:") << " " << max_attacks << '\n';
878  }
879  if ( min_attacks != 0 ) {
880  tooltip << '\t' << _("Subject to swarm:") << " " << (max_attacks-min_attacks) << '\n';
881  }
882  tooltip << '\t' << _("Swarm:") << " " << "* "<< (cur_hp*100/max_hp) << "%\n";
883  }
884  }
885  else if ( num_attacks != base_attacks ) {
886  tooltip << '\t' << _("Base attacks:") << " " << base_attacks << '\n';
887  tooltip << '\t' << _("With specials:") << " " << num_attacks << '\n';
888  }
889 
890  const string_with_tooltip damage_and_num_attacks {flush(str), flush(tooltip)};
891 
892  std::string range = string_table["range_" + at.range()];
893  std::pair<std::string, std::set<std::string>> all_damage_types = at.damage_types();
894  std::string type = all_damage_types.first;
895  std::set<std::string> alt_types = all_damage_types.second;
896  std::string lang_type = string_table["type_" + type];
897  for(auto alt_t : alt_types){
898  lang_type += ", " + string_table["type_" + alt_t];
899  }
900 
901  // SCALE_INTO() is needed in case the 72x72 images/misc/missing-image.png is substituted.
902  const std::string range_png = std::string("icons/profiles/") + at.range() + "_attack.png~SCALE_INTO(16,16)";
903  const std::string type_png = std::string("icons/profiles/") + type + ".png~SCALE_INTO(16,16)";
904  std::vector<std::string> secondary_types_png;
905  for(const auto& alt_t : alt_types) {
906  secondary_types_png.push_back(std::string("icons/profiles/") + alt_t + ".png~SCALE_INTO(16,16)");
907  }
908 
909  // If any of the images is missing, then add a text description too.
910  bool all_pngs_exist = image::exists(range_png);
911  all_pngs_exist &= image::exists(type_png);
912  for(const auto& png : secondary_types_png) {
913  all_pngs_exist &= image::exists(png);
914  }
915  if(!all_pngs_exist) {
916  str << span_color(font::weapon_details_color, " ", " ", range, font::weapon_details_sep, lang_type)
917  << "\n";
918  }
919 
920  tooltip << _("Weapon range:") << " " << markup::bold(range) << "\n"
921  << _("Damage type:") << " " << markup::bold(lang_type) << "\n"
922  << _("Damage versus:") << " " << '\n';
923 
924  // Show this weapon damage and resistance against all the different units.
925  // We want weak resistances (= good damage) first.
926  std::map<int, std::set<std::string>, std::greater<int>> resistances;
927  std::set<std::string> seen_types;
928  const team &unit_team = rc.dc().get_team(u.side());
929  const team &viewing_team = rc.screen().viewing_team();
930  for (const unit &enemy : rc.units())
931  {
932  if (enemy.incapacitated()) //we can't attack statues so don't display them in this tooltip
933  continue;
934  if (!unit_team.is_enemy(enemy.side()))
935  continue;
936  const map_location &loc = enemy.get_location();
937  const bool see_all = game_config::debug || rc.screen().show_everything();
938  if (!enemy.is_visible_to_team(viewing_team, see_all))
939  continue;
940  bool new_type = seen_types.insert(enemy.type_id()).second;
941  if (new_type) {
942  auto ctx = specials_context_t::make(
943  { u.shared_from_this(), hex, at.shared_from_this() },
944  { enemy.shared_from_this(), loc, nullptr },
945  attacking);
946  const auto [damage_type, resistance] = weapon->effective_damage_type();
947  resistances[resistance].insert(enemy.type_name());
948  }
949  }
950 
951  for (const auto& resist : resistances) {
952  int damage_with_resistance = round_damage(specials_damage, damage_multiplier * resist.first, damage_divisor);
953  tooltip << markup::bold(damage_with_resistance) << " "
954  << span_color(attack_info_percent_color(resist.first-100),
955  markup::italic("(", utils::signed_percent(resist.first-100), ")"))
956  << " : \t" // spaces to align the tab to a multiple of 8
957  << utils::join(resist.second, " " + font::unicode_bullet + " ") << '\n';
958  }
959  const string_with_tooltip damage_versus {flush(str), flush(tooltip)};
960 
961 #if 0
962  // We wanted to use the attack icon here, but couldn't find a good layout.
963  // The default images are 60x60 and have a 2-pixel transparent border. Trim it.
964  // The first SCALE() accounts for theoretically possible add-ons attack images larger than 60x60.
965  const std::string attack_icon = at.icon() + "~SCALE_INTO_SHARP(60,60)~CROP(2,2,56,56)~SCALE_INTO_SHARP(32,32)";
966  add_image(res, attack_icon, at.name());
967  add_text(res, " ", "");
968 #endif
969 
970  // The icons are 16x16. We add 5px padding for alignment reasons (placement of the icon in relation to ascender and descender letters).
971  const std::string spacer = "misc/blank.png~CROP(0, 0, 16, 21)"; // 21 == 16+5
972  add_image(res, spacer + "~BLIT(" + range_png + ",0,5)", damage_versus.tooltip);
973  add_image(res, spacer + "~BLIT(" + type_png + ",0,5)", damage_versus.tooltip);
974  for(auto sec_exist : secondary_types_png){
975  if(image::exists(sec_exist)){
976  add_image(res, spacer + "~BLIT(" + sec_exist + ",0,5)", damage_versus.tooltip);
977  }
978  }
979  add_text(res, damage_and_num_attacks.str, damage_and_num_attacks.tooltip);
980  add_text(res, damage_versus.str, damage_versus.tooltip); // This string is usually empty
981 
982  if(attack_alignment != u.alignment()){
983  const std::string align = unit_type::alignment_description(attack_alignment, u.gender());
984  const std::string align_id = unit_alignments::get_string(attack_alignment);
985 
986  color_t color = font::weapon_color;
987  if(tod_bonus != 0) {
988  color = (tod_bonus > 0) ? font::good_dmg_color : font::bad_dmg_color;
989  }
990 
991  str << " " << align << " (" << span_color(color, utils::signed_percent(tod_bonus)) << ")" << "\n";
992 
993  tooltip << _("Alignment:") << " " << markup::bold(align) << "\n"
994  << string_table[align_id + "_description" ] + "\n";
995 
996  add_text(res, flush(str), flush(tooltip));
997  }
998 
999  if(at.attacks_used() > 1)
1000  {
1001  const std::string& attacks_used = VNGETTEXT(
1002  "uses $num attack",
1003  "uses $num attacks",
1004  at.attacks_used(),
1005  { {"num", std::to_string(at.attacks_used())} }
1006  );
1007  str << span_color(font::weapon_details_color, " ", attacks_used) << "\n";
1008 
1009  const std::string& attacks_used_tooltip = VNGETTEXT(
1010  "This attack uses $num attack point",
1011  "This attack uses $num attack points",
1012  at.attacks_used(),
1013  { {"num", std::to_string(at.attacks_used())} }
1014  );
1015  add_text(res, flush(str), attacks_used_tooltip);
1016  }
1017 
1018  const std::string &accuracy_parry = at.accuracy_parry_description();
1019  if (!accuracy_parry.empty())
1020  {
1021  str << span_color(font::weapon_details_color, " ", accuracy_parry) << "\n";
1022  add_text(res, flush(str), at.accuracy_parry_tooltip());
1023  }
1024  }
1025 
1026  {
1027  //If we have a second unit, do the 2-unit specials_context
1028  auto ctx = specials_context_t::make(
1029  { u.shared_from_this(), hex, at.shared_from_this() },
1030  { sec_u ? sec_u->shared_from_this() : nullptr, sec_u ? sec_u->get_location() : map_location(), sec_u_weapon },
1031  attacking);
1032 
1033  ctx.set_for_listing(true);
1034 
1035  boost::dynamic_bitset<> active;
1036  auto specials = ctx.special_tooltips(at, active);
1037  const std::size_t specials_size = specials.size();
1038  for ( std::size_t i = 0; i != specials_size; ++i )
1039  {
1040  // Aliases for readability:
1041  const auto& name = specials[i].name;
1042  const auto& description = specials[i].description;
1043  const color_t &details_color =
1045 
1046  str << span_color(details_color, " ", " ", name) << '\n';
1047  std::string help_page = "weaponspecial_" + specials[i].help_topic_id;
1048  tooltip << _("Weapon special:") << " " << markup::bold(name);
1049  if (!active[i]) {
1050  tooltip << markup::italic(_(" (inactive)"));
1051  }
1052  tooltip << '\n' << description;
1053 
1054  add_text(res, flush(str), flush(tooltip), help_page);
1055  }
1056 
1057  if(!specials.empty()) {
1058  // Add some padding so the end of the specials list
1059  // isn't too close vertically to the attack icons of
1060  // the next attack. Also for symmetry with the padding
1061  // above the list of specials (below the attack icon line).
1062  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1063  add_image(res, spacer, "");
1064  add_text(res, "\n", "");
1065  }
1066  }
1067 
1068  // 'abilities' version of special_tooltips is below.
1069  {
1070  //If we have a second unit, do the 2-unit specials_context
1071  auto ctx = specials_context_t::make(
1072  { u.shared_from_this(), hex, at.shared_from_this() },
1073  { sec_u ? sec_u->shared_from_this() : nullptr, sec_u ? sec_u->get_location() : map_location(), sec_u_weapon },
1074  attacking);
1075 
1076  boost::dynamic_bitset<> active;
1077  auto specials = ctx.abilities_special_tooltips(at, active);
1078  const std::size_t specials_size = specials.size();
1079  for ( std::size_t i = 0; i != specials_size; ++i )
1080  {
1081  // Aliases for readability:
1082  const auto& [name, description, help_topic_id] = specials[i];
1083  const color_t& details_color =
1085 
1086  str << span_color(details_color, " ", " ", name) << '\n';
1087  const std::string help_page = "weaponspecial_" + help_topic_id;
1088  tooltip << _("Weapon special:") << " " << markup::bold(name);
1089  if (!active[i]) {
1090  tooltip << markup::italic(_(" (inactive)"));
1091  }
1092  tooltip << '\n' << description;
1093 
1094  add_text(res, flush(str), flush(tooltip), help_page);
1095  }
1096 
1097  if(!specials.empty()) {
1098  // Add some padding so the end of the specials list
1099  // isn't too close vertically to the attack icons of
1100  // the next attack. Also for symmetry with the padding
1101  // above the list of specials (below the attack icon line).
1102  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1103  add_image(res, spacer, "");
1104  add_text(res, "\n", "");
1105  }
1106  }
1107  return damage;
1108 }
1109 
1110 // Conversion routine for both unscathed and damage change percentage.
1111 static std::string format_prob(double prob)
1112 {
1113  if(prob > 0.9995) {
1114  return "100%";
1115  } else if(prob < 0.0005) {
1116  return "0%";
1117  }
1118  std::ostringstream res;
1119  res << std::setprecision(prob < 0.01 ? 1 : prob < 0.1 ? 2 : 3) << 100.0 * prob << "%";
1120  return res.str();
1121 }
1122 
1123 static std::string format_hp(unsigned hp)
1124 {
1125  std::ostringstream res;
1126  res << ' ' << std::setw(3) << hp;
1127  return res.str();
1128 }
1129 
1130 static config unit_weapons(const reports::context& rc, const unit_const_ptr& attacker, const map_location &attacker_pos, const unit *defender, bool show_attacker)
1131 {
1132  if (!attacker || !defender) return config();
1133 
1134  const unit* u = show_attacker ? attacker.get() : defender;
1135  const unit* sec_u = !show_attacker ? attacker.get() : defender;
1136  const map_location unit_loc = show_attacker ? attacker_pos : defender->get_location();
1137 
1138  std::ostringstream str, tooltip;
1139  config res;
1140 
1141  std::vector<battle_context> weapons;
1142  for (unsigned i = 0; i < attacker->attacks().size(); i++) {
1143  // skip weapons with attack_weight=0
1144  if (attacker->attacks()[i].attack_weight() > 0) {
1145  weapons.emplace_back(rc.units(), attacker_pos, defender->get_location(), i, -1, 0.0, nullptr, attacker);
1146  }
1147  }
1148 
1149  for (const battle_context& weapon : weapons) {
1150 
1151  // Predict the battle outcome.
1152  combatant attacker_combatant(weapon.get_attacker_stats());
1153  combatant defender_combatant(weapon.get_defender_stats());
1154  attacker_combatant.fight(defender_combatant);
1155 
1156  const battle_context_unit_stats& context_unit_stats =
1157  show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1158  const battle_context_unit_stats& other_context_unit_stats =
1159  !show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1160 
1161  int total_damage = 0;
1162  int base_damage = 0;
1163  int num_blows = 0;
1164  int chance_to_hit = 0;
1165  t_string weapon_name = _("weapon^None");
1166 
1167  color_t dmg_color = font::weapon_color;
1168  if (context_unit_stats.weapon) {
1169  base_damage = attack_info(rc, *context_unit_stats.weapon, res, *u, unit_loc, sec_u, other_context_unit_stats.weapon);
1170  total_damage = context_unit_stats.damage;
1171  num_blows = context_unit_stats.num_blows;
1172  chance_to_hit = context_unit_stats.chance_to_hit;
1173  weapon_name = context_unit_stats.weapon->name();
1174 
1175  if ( total_damage > base_damage ) {
1176  dmg_color = font::good_dmg_color;
1177  } else if ( total_damage < base_damage ) {
1178  dmg_color = font::bad_dmg_color;
1179  }
1180  } else {
1181  str << span_color(font::weapon_color, weapon_name) << "\n";
1182  tooltip << _("Weapon:") << " " << markup::bold(weapon_name) << "\n"
1183  << _("Damage:") << " " << markup::bold("0") << "\n";
1184  }
1185 
1186  color_t chance_color = game_config::red_to_green(chance_to_hit);
1187 
1188  // Total damage.
1189  str << " " << span_color(dmg_color, total_damage)
1191  font::unicode_en_dash, num_blows, " (", span_color(chance_color, chance_to_hit, "%"), ")")
1192  << "\n";
1193 
1194  tooltip << _("Weapon:") << " " << markup::bold(weapon_name) << "\n"
1195  << _("Total damage") << markup::bold(total_damage) << "\n";
1196 
1197  // Create the hitpoints distribution.
1198  std::vector<std::pair<int, double>> hp_prob_vector;
1199 
1200  // First, we sort the probabilities in ascending order.
1201  std::vector<std::pair<double, int>> prob_hp_vector;
1202 
1203  combatant* c = show_attacker ? &attacker_combatant : &defender_combatant;
1204 
1205  int i = 0;
1206  for (double prob : c->hp_dist) {
1207  // We keep only values above 0.1%.
1208  if(prob > 0.001) {
1209  prob_hp_vector.emplace_back(prob, i);
1210  }
1211  i++;
1212  }
1213 
1214  std::sort(prob_hp_vector.begin(), prob_hp_vector.end());
1215 
1216  //TODO fendrin -- make that dynamically
1217  std::size_t max_hp_distrib_rows_ = 10;
1218 
1219  // We store a few of the highest probability hitpoint values.
1220  std::size_t nb_elem = std::min<std::size_t>(max_hp_distrib_rows_, prob_hp_vector.size());
1221 
1222  for(std::size_t i = prob_hp_vector.size() - nb_elem; i <prob_hp_vector.size(); i++) {
1223  hp_prob_vector.emplace_back(prob_hp_vector[i].second, prob_hp_vector[i].first);
1224  }
1225 
1226  // Then, we sort the hitpoint values in ascending order.
1227  std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
1228  // And reverse the order. Might be doable in a better manor.
1229  std::reverse(hp_prob_vector.begin(), hp_prob_vector.end());
1230 
1231  for (const auto& [hp, prob] : hp_prob_vector) {
1232  color_t prob_color = game_config::blue_to_white(prob * 100.0, true);
1233 
1235  " ", " ", span_color(u->hp_color(hp), format_hp(hp)),
1236  " ", font::weapon_numbers_sep, " ", span_color(prob_color, format_prob(prob)))
1237  << "\n";
1238  }
1239 
1240  add_text(res, flush(str), flush(tooltip));
1241  }
1242  return res;
1243 }
1244 
1245 /*
1246  * Display the attacks of the displayed unit against the unit passed as argument.
1247  * 'hex' is the location the attacker will be at during combat.
1248  */
1249 static config unit_weapons(const reports::context& rc, const unit* u, const map_location& hex)
1250 {
1251  config res;
1252  if ((u != nullptr) && (!u->attacks().empty())) {
1253  const std::string attack_headline = _n("Attack", "Attacks", u->attacks().size());
1254 
1255  add_text(res, span_color(font::weapon_details_color, attack_headline) + "\n", "");
1256 
1257  const auto left = u->attacks_left(false), max = u->max_attacks();
1258  if(max != 1) {
1259  // TRANSLATORS: This string is shown in the sidebar beneath the word "Attacks" when a unit can attack multiple times per turn
1260  const std::string line = VGETTEXT("Remaining: $left/$max",
1261  {{"left", std::to_string(left)},
1262  {"max", std::to_string(max)}});
1263  add_text(res, " " + span_color(font::weapon_details_color, line) + "\n",
1264  _("This unit can attack multiple times per turn."));
1265  }
1266 
1267  for (const attack_type& at : u->attacks())
1268  {
1269  attack_info(rc, at, res, *u, hex);
1270  }
1271  }
1272  return res;
1273 }
1275 {
1276  const unit *u = get_visible_unit(rc);
1277  const map_location& mouseover_hex = rc.screen().mouseover_hex();
1278  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
1279  const map_location& hex = mouseover_hex.valid() ? mouseover_hex : displayed_unit_hex;
1280  if (!u) return config();
1281 
1282  return unit_weapons(rc, u, hex);
1283 }
1284 REPORT_GENERATOR(highlighted_unit_weapons, rc)
1285 {
1287  const unit *sec_u = get_visible_unit(rc);
1288 
1289  if (!u) return report_unit_weapons(rc);
1290  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1291 
1292  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1293  map_location attack_loc;
1294  if (rc.mhb())
1295  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1296 
1297  if (!attack_loc.valid())
1298  return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1299 
1300  //TODO: shouldn't this pass sec_u as secodn parameter ?
1301  return unit_weapons(rc, u, attack_loc, sec_u, false);
1302 }
1303 REPORT_GENERATOR(selected_unit_weapons, rc)
1304 {
1306  const unit *sec_u = get_visible_unit(rc);
1307 
1308  if (!u) return config();
1309  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, u.get(), u->get_location());
1310 
1311  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1312  map_location attack_loc;
1313  if (rc.mhb())
1314  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1315 
1316  if (!attack_loc.valid())
1317  return unit_weapons(rc, u.get(), u->get_location());
1318 
1319  return unit_weapons(rc, u, attack_loc, sec_u, true);
1320 }
1321 
1322 REPORT_GENERATOR(unit_image,rc)
1323 {
1324  const unit *u = get_visible_unit(rc);
1325  if (!u) return config();
1326  return image_report(u->absolute_image() + u->image_mods());
1327 }
1328 REPORT_GENERATOR(selected_unit_image, rc)
1329 {
1330  const unit *u = get_selected_unit(rc);
1331  if (!u) return config();
1332  return image_report(u->absolute_image() + u->image_mods());
1333 }
1334 
1335 REPORT_GENERATOR(selected_unit_profile, rc)
1336 {
1337  const unit *u = get_selected_unit(rc);
1338  if (!u) return config();
1339  return image_report(u->small_profile());
1340 }
1341 REPORT_GENERATOR(unit_profile, rc)
1342 {
1343  const unit *u = get_visible_unit(rc);
1344  if (!u) return config();
1345  return image_report(u->small_profile());
1346 }
1347 
1348 static config tod_stats_at(const reports::context& rc, const map_location& hex)
1349 {
1350  std::ostringstream tooltip;
1351  std::ostringstream text;
1352 
1353  const map_location& tod_schedule_hex = (hex.valid() && !display::get_singleton()->shrouded(hex)) ? hex : map_location::null_location();
1354  const std::vector<time_of_day>& schedule = rc.tod().times(tod_schedule_hex);
1355 
1356  tooltip << _("Time of day schedule:") << " \n";
1357  int current = rc.tod().get_current_time(tod_schedule_hex);
1358  int i = 0;
1359  for (const time_of_day& tod : schedule) {
1360  if (i == current) {
1361  tooltip << markup::tag("big", markup::bold(tod.name)) << "\n";
1362  } else {
1363  tooltip << tod.name << "\n";
1364  }
1365  i++;
1366  }
1367 
1368  int times = schedule.size();
1369  text << current + 1 << "/" << times;
1370 
1371  return text_report(text.str(), tooltip.str(), "..schedule");
1372 }
1373 REPORT_GENERATOR(tod_stats, rc)
1374 {
1375  map_location mouseover_hex = rc.screen().mouseover_hex();
1376  if (mouseover_hex.valid()) return tod_stats_at(rc, mouseover_hex);
1377  return tod_stats_at(rc, rc.screen().selected_hex());
1378 }
1379 REPORT_GENERATOR(selected_tod_stats, rc)
1380 {
1381  const unit *u = get_selected_unit(rc);
1382  if(!u) return tod_stats_at(rc, map_location::null_location());
1383  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1384  const map_location& hex =
1385  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1386  return tod_stats_at(rc, hex);
1387 }
1388 
1389 static config time_of_day_at(const reports::context& rc, const map_location& mouseover_hex)
1390 {
1391  std::ostringstream tooltip;
1392  time_of_day tod = get_visible_time_of_day_at(rc, mouseover_hex);
1393 
1394  int b = tod.lawful_bonus;
1395  int l = generic_combat_modifier(b, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1396  std::string lawful_color("white");
1397  std::string chaotic_color("white");
1398  std::string liminal_color("white");
1399 
1400  if (b != 0) {
1401  lawful_color = (b > 0) ? "#0f0" : "#f00";
1402  chaotic_color = (b < 0) ? "#0f0" : "#f00";
1403  }
1404  if (l != 0) {
1405  liminal_color = (l > 0) ? "#0f0" : "#f00";
1406  }
1407  tooltip << _("Time of day:") << " " << markup::bold(tod.name) << "\n"
1408  << _("Lawful units:") << " "
1409  << markup::span_color(lawful_color, utils::signed_percent(b)) << "\n"
1410  << _("Neutral units:") << " " << utils::signed_percent(0) << '\n'
1411  << _("Chaotic units:") << " "
1412  << markup::span_color(chaotic_color, utils::signed_percent(-b)) << "\n"
1413  << _("Liminal units:") << " "
1414  << markup::span_color(liminal_color, utils::signed_percent(l)) << "\n";
1415 
1416  std::string tod_image = tod.image;
1417  if(tod.bonus_modified > 0) {
1418  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_bright << ")").str();
1419  } else if(tod.bonus_modified < 0) {
1420  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_dark << ")").str();
1421  }
1422 
1423  return image_report(tod_image, tooltip.str(), "time_of_day_" + tod.id);
1424 }
1426 {
1427  map_location mouseover_hex = rc.screen().mouseover_hex();
1428  if (mouseover_hex.valid()) return time_of_day_at(rc, mouseover_hex);
1429  return time_of_day_at(rc, rc.screen().selected_hex());
1430 }
1431 REPORT_GENERATOR(selected_time_of_day, rc)
1432 {
1433  const unit *u = get_selected_unit(rc);
1434  if(!u) return time_of_day_at(rc, map_location::null_location());
1435  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1436  const map_location& hex =
1437  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1438  return time_of_day_at(rc, hex);
1439 }
1440 
1441 static config unit_box_at(const reports::context& rc, const map_location& mouseover_hex)
1442 {
1443  std::ostringstream tooltip;
1444  time_of_day global_tod = rc.tod().get_time_of_day();
1445  time_of_day local_tod = get_visible_time_of_day_at(rc, mouseover_hex);
1446 
1447  int bonus = local_tod.lawful_bonus;
1448  int bonus_lim = generic_combat_modifier(bonus, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1449 
1450  std::string lawful_color("white");
1451  std::string chaotic_color("white");
1452  std::string liminal_color("white");
1453 
1454  if (bonus != 0) {
1455  lawful_color = (bonus > 0) ? "green" : "red";
1456  chaotic_color = (bonus < 0) ? "green" : "red";
1457  }
1458  if (bonus_lim != 0) {
1459  liminal_color = (bonus_lim > 0) ? "green" : "red";
1460  }
1461  tooltip << local_tod.name << '\n'
1462  << _("Lawful units:") << " "
1463  << markup::span_color(lawful_color, utils::signed_percent(bonus)) << "\n"
1464  << _("Neutral units:") << " " << utils::signed_percent(0) << '\n'
1465  << _("Chaotic units:") << " "
1466  << markup::span_color(chaotic_color, utils::signed_percent(-bonus)) << "\n"
1467  << _("Liminal units:") << " "
1468  << markup::span_color(liminal_color, utils::signed_percent(bonus_lim)) << "\n";
1469 
1470  std::string local_tod_image = "themes/classic/" + local_tod.image;
1471  std::string global_tod_image = "themes/classic/" + global_tod.image;
1472  if(local_tod.bonus_modified != 0) {
1473  local_tod_image += "~BLIT(";
1474  if (local_tod.bonus_modified > 0) {
1475  local_tod_image += game_config::images::tod_bright;
1476  } else if (local_tod.bonus_modified < 0) {
1477  local_tod_image += game_config::images::tod_dark;
1478  }
1479  local_tod_image += ")";
1480  }
1481 
1482  const gamemap &map = rc.map();
1483 
1484  //if (t_translation::terrain_matches(terrain, t_translation::ALL_OFF_MAP))
1485  // return config();
1486 
1487  //if (map.is_keep(mouseover_hex)) {
1488  // add_image(cfg, "icons/terrain/terrain_type_keep.png", "");
1489  //}
1490 
1491  std::string bg_terrain_image;
1492 
1493  for(const t_translation::terrain_code& underlying_terrain : map.get_terrain_info(mouseover_hex).union_type()) {
1494  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1495  bg_terrain_image = "~BLIT(unit_env/terrain/terrain-" + terrain_id + ".png)" + bg_terrain_image;
1496  }
1497 
1498  std::stringstream color;
1499  color << local_tod.color;
1500 
1501  bg_terrain_image = bg_terrain_image + "~CS(" + color.str() + ")";
1502 
1503  const unit* u = get_visible_unit(rc);
1504  std::string unit_image;
1505  if (u) {
1506  unit_image = "~BLIT(" + u->absolute_image() + u->image_mods() + ",35,22)";
1507  }
1508 
1509  std::string tod_image = global_tod_image + "~BLIT(" + local_tod_image + ")";
1510 
1511  return image_report(tod_image + bg_terrain_image + unit_image, tooltip.str(), "time_of_day");
1512 }
1513 REPORT_GENERATOR(unit_box, rc)
1514 {
1515  map_location mouseover_hex = rc.screen().mouseover_hex();
1516  return unit_box_at(rc, mouseover_hex);
1517 }
1518 
1519 
1521 {
1522  std::ostringstream str, tooltip;
1523  str << rc.tod().turn();
1524  int nb = rc.tod().number_of_turns();
1525  tooltip << _("Turn Number");
1526 
1527  if(nb != -1) {
1528  str << '/' << nb;
1529  tooltip << "\n\n" << _("When the game exceeds the number of turns indicated by the second number, it will end.");
1530  }
1531  return text_report(str.str(), tooltip.str());
1532 }
1533 
1535 {
1536  std::ostringstream str;
1537  const team& viewing_team = rc.screen().viewing_team();
1538  // Suppose the full unit map is applied.
1539  int fake_gold = viewing_team.gold();
1540  if (rc.wb()) {
1541  fake_gold -= rc.wb()->get_spent_gold_for(viewing_team.side());
1542  }
1543 
1544  if (!rc.screen().viewing_team_is_playing()) {
1546  } else if (fake_gold < 0) {
1548  } else {
1549  str << utils::half_signed_value(fake_gold);
1550  }
1551 
1552  return text_report(str.str(), _("Gold") + "\n\n" + _("The amount of gold currently available to recruit and maintain your army."));
1553 }
1554 
1555 REPORT_GENERATOR(villages, rc)
1556 {
1557  std::ostringstream str;
1558  const team &viewing_team = rc.screen().viewing_team();
1559  str << viewing_team.villages().size() << '/';
1560  if (viewing_team.uses_shroud()) {
1561  int unshrouded_villages = 0;
1562  for (const map_location &loc : rc.map().villages()) {
1563  if (!viewing_team.shrouded(loc))
1564  ++unshrouded_villages;
1565  }
1566  str << unshrouded_villages;
1567  } else {
1568  str << rc.map().villages().size();
1569  }
1570  return gray_inactive(rc,str.str(), _("Villages") + "\n\n" + _("The fraction of known villages that your side has captured."));
1571 }
1572 
1573 REPORT_GENERATOR(num_units, rc)
1574 {
1575  return gray_inactive(rc, std::to_string(rc.dc().side_units(rc.screen().viewing_team().side())), _("Units") + "\n\n" + _("The total number of units on your side."));
1576 }
1577 
1578 REPORT_GENERATOR(upkeep, rc)
1579 {
1580  std::ostringstream str;
1581  const team& viewing_team = rc.screen().viewing_team();
1582  team_data td(rc.dc(), viewing_team);
1583  str << td.expenses << " (" << td.upkeep << ")";
1584  return gray_inactive(rc,str.str(), _("Upkeep") + "\n\n" + _("The expenses incurred at the end of every turn to maintain your army. The first number is the amount of gold that will be deducted. It is equal to the number of unit levels not supported by villages. The second is the total cost of upkeep, including that covered by villages — in other words, the amount of gold that would be deducted if you lost all villages."));
1585 }
1586 
1587 REPORT_GENERATOR(expenses, rc)
1588 {
1589  const team& viewing_team = rc.screen().viewing_team();
1590  team_data td(rc.dc(), viewing_team);
1591  return gray_inactive(rc,std::to_string(td.expenses));
1592 }
1593 
1594 REPORT_GENERATOR(income, rc)
1595 {
1596  std::ostringstream str;
1597  const team& viewing_team = rc.screen().viewing_team();
1598  team_data td(rc.dc(), viewing_team);
1599 
1600  if (!rc.screen().viewing_team_is_playing()) {
1601  if (td.net_income < 0) {
1602  td.net_income = - td.net_income;
1604  } else {
1606  }
1607  } else if (td.net_income < 0) {
1608  td.net_income = - td.net_income;
1610  } else {
1611  str << td.net_income;
1612  }
1613 
1614  return text_report(str.str(), _("Net Income") + "\n\n" + _("The net amount of gold you gain or lose each turn, taking into account income from controlled villages and payment of upkeep."));
1615 }
1616 
1617 namespace {
1618 void blit_tced_icon(config &cfg, const std::string &terrain_id, const std::string &icon_image, bool high_res,
1619  const std::string &terrain_name) {
1620  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
1621  const std::string terrain_image = "terrain/" + icon_image + (high_res ? "_30.png" : ".png");
1622  add_image(cfg, tc_base + "~RC(magenta>" + terrain_id + ")~BLIT(" + terrain_image + ")", terrain_name);
1623 }
1624 }
1625 
1626 REPORT_GENERATOR(terrain_info, rc)
1627 {
1628  const gamemap& map = rc.map();
1629  map_location mouseover_hex = rc.screen().mouseover_hex();
1630 
1631  if(!map.on_board(mouseover_hex)) {
1632  mouseover_hex = rc.screen().selected_hex();
1633  }
1634 
1635  if(!map.on_board(mouseover_hex)) {
1636  return config();
1637  }
1638 
1639  const terrain_type& terrain = map.get_terrain_info(mouseover_hex);
1641  return config();
1642  }
1643 
1644  config cfg;
1645 
1646  bool high_res = false;
1647 
1648  if(display::get_singleton()->shrouded(mouseover_hex)) {
1649  return cfg;
1650  }
1651  //TODO
1652 // if (display::get_singleton()->fogged(mouseover_hex)) {
1653 // blit_tced_icon(cfg, "fog", high_res);
1654 // }
1655 //
1656 // if (map.is_keep(mouseover_hex)) {
1657 // blit_tced_icon(cfg, "keep", high_res);
1658 // }
1659 
1660  for(const t_translation::terrain_code& underlying_terrain : terrain.union_type()) {
1662  continue;
1663  }
1664  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1665  const std::string& terrain_name = map.get_terrain_string(underlying_terrain);
1666  const std::string& terrain_icon = map.get_terrain_info(underlying_terrain).icon_image();
1667  if(terrain_icon.empty()) {
1668  continue;
1669  }
1670  blit_tced_icon(cfg, terrain_id, terrain_icon, high_res, terrain_name);
1671  }
1672 
1673  if(map.is_village(mouseover_hex)) {
1674  int owner = rc.dc().village_owner(mouseover_hex);
1675  // This report is used in both game and editor. get_team(viewing_side) would throw in the editor's
1676  // terrain-only mode, but if the village already has an owner then we're not in that mode.
1677  if(owner != 0) {
1678  const team& viewing_team = rc.screen().viewing_team();
1679 
1680  if(!viewing_team.fogged(mouseover_hex)) {
1681  const team& owner_team = rc.dc().get_team(owner);
1682 
1683  std::string flag_icon = owner_team.flag_icon();
1684  std::string old_rgb = game_config::flag_rgb;
1685  std::string new_rgb = team::get_side_color_id(owner_team.side());
1686  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1687  if(flag_icon.empty()) {
1689  }
1690  std::string tooltip = side_tooltip(owner_team);
1691  std::string side = std::to_string(owner_team.side());
1692 
1693  add_image(cfg, flag_icon + mods, tooltip);
1694  add_text(cfg, side, tooltip);
1695  }
1696  }
1697  }
1698 
1699  return cfg;
1700 }
1701 
1702 REPORT_GENERATOR(terrain, rc)
1703 {
1704  const gamemap &map = rc.map();
1705  const team& viewing_team = rc.screen().viewing_team();
1706  map_location mouseover_hex = rc.screen().mouseover_hex();
1707  if (!map.on_board(mouseover_hex) || viewing_team.shrouded(mouseover_hex))
1708  return config();
1709 
1710  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1712  return config();
1713 
1714  std::ostringstream str;
1715  if (map.is_village(mouseover_hex))
1716  {
1717  int owner = rc.dc().village_owner(mouseover_hex);
1718  if (owner == 0 || viewing_team.fogged(mouseover_hex)) {
1719  str << map.get_terrain_info(terrain).income_description();
1720  } else if (owner == viewing_team.side()) {
1721  str << map.get_terrain_info(terrain).income_description_own();
1722  } else if (viewing_team.is_enemy(owner)) {
1723  str << map.get_terrain_info(terrain).income_description_enemy();
1724  } else {
1725  str << map.get_terrain_info(terrain).income_description_ally();
1726  }
1727 
1728  const std::string& underlying_desc = map.get_underlying_terrain_string(mouseover_hex);
1729  if(!underlying_desc.empty()) {
1730  str << underlying_desc;
1731  }
1732  } else {
1733  str << map.get_terrain_string(terrain);
1734  }
1735 
1736  return text_report(str.str());
1737 }
1738 
1739 REPORT_GENERATOR(zoom_level, rc)
1740 {
1741  std::ostringstream text;
1742  std::ostringstream tooltip;
1743  std::ostringstream help;
1744 
1745  text << static_cast<int>(rc.screen().get_zoom_factor() * 100) << "%";
1746 
1747  return text_report(text.str(), tooltip.str(), help.str());
1748 }
1749 
1750 REPORT_GENERATOR(position, rc)
1751 {
1752  const gamemap &map = rc.map();
1753  map_location mouseover_hex = rc.screen().mouseover_hex(),
1754  displayed_unit_hex = rc.screen().displayed_unit_hex(),
1755  selected_hex = rc.screen().selected_hex();
1756 
1757  if (!map.on_board(mouseover_hex)) {
1758  if (!map.on_board(selected_hex))
1759  return config();
1760  else {
1761  mouseover_hex = selected_hex;
1762  }
1763  }
1764 
1765  t_translation::terrain_code terrain = map[mouseover_hex];
1767  return config();
1768 
1769  std::ostringstream str;
1770  str << mouseover_hex;
1771 
1772  const unit *u = get_visible_unit(rc);
1773  const team &viewing_team = rc.screen().viewing_team();
1774  if (!u ||
1775  (displayed_unit_hex != mouseover_hex
1776  && displayed_unit_hex != rc.screen().selected_hex())
1777  || viewing_team.shrouded(mouseover_hex))
1778  {
1779  return text_report(str.str());
1780  }
1781 
1782  int move_cost = u->movement_cost(terrain);
1783  int defense = 100 - u->defense_modifier(terrain, mouseover_hex);
1784  if (move_cost < movetype::UNREACHABLE) {
1785  str << " " << defense << "%," << move_cost;
1786  } else if (mouseover_hex == displayed_unit_hex) {
1787  str << " " << defense << "%,‒";
1788  } else {
1789  str << " ‒";
1790  }
1791  return text_report(str.str());
1792 }
1793 
1794 REPORT_GENERATOR(side_playing, rc)
1795 {
1796  const team &active_team = rc.screen().playing_team();
1797  std::string flag_icon = active_team.flag_icon();
1798  std::string old_rgb = game_config::flag_rgb;
1799  std::string new_rgb = team::get_side_color_id(rc.screen().playing_team().side());
1800  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1801  if (flag_icon.empty()) {
1803  }
1804  return image_report(flag_icon + mods, side_tooltip(active_team));
1805 }
1806 
1807 REPORT_GENERATOR(observers, rc)
1808 {
1809  const std::set<std::string> &observers = rc.screen().observers();
1810  if (observers.empty())
1811  return config();
1812 
1813  std::ostringstream str;
1814  str << _("Observers:") << '\n';
1815  for (const std::string &obs : observers) {
1816  str << obs << '\n';
1817  }
1818  return image_report(game_config::images::observer, str.str());
1819 }
1820 
1821 REPORT_GENERATOR(report_clock, /*rc*/)
1822 {
1823  config report;
1825 
1827  ? "%I:%M %p"
1828  : "%H:%M";
1829 
1830  const auto now = std::chrono::system_clock::now();
1831  add_text(report, chrono::format_local_timestamp(now, format), _("Clock"));
1832 
1833  return report;
1834 }
1835 
1836 
1837 REPORT_GENERATOR(battery, /*rc*/)
1838 {
1839  config report;
1840 
1842  add_text(report, (boost::format("%.0f %%") % desktop::battery_info::get_battery_percentage()).str(), _("Battery"));
1843 
1844  return report;
1845 }
1846 
1847 REPORT_GENERATOR(report_countdown, rc)
1848 {
1849  using namespace std::chrono_literals;
1850  const team& viewing_team = rc.screen().viewing_team();
1851  if (viewing_team.countdown_time() == 0ms) {
1852  return report_report_clock(rc);
1853  }
1854 
1855  std::ostringstream time_str, formatted_time_str;
1856 
1857  using std::chrono::duration_cast;
1858 #ifdef __cpp_lib_format
1859  auto sec = duration_cast<std::chrono::seconds>(viewing_team.countdown_time());
1860  time_str << std::format("{:%M:%S}", sec);
1861 #else
1862  auto fmt = [](const auto& duration) -> std::string {
1863  return formatter{} << std::setw(2) << std::setfill('0') << duration.count();
1864  };
1865 
1866  // Create the time string
1867  auto sec = duration_cast<std::chrono::seconds>(viewing_team.countdown_time());
1868  auto min = duration_cast<std::chrono::minutes>(sec);
1869  time_str << fmt(min) << ':' << fmt(sec - min);
1870 #endif
1871 
1872  // Colorize the time string
1873  if (!rc.screen().viewing_team_is_playing()) {
1874  formatted_time_str << span_color(font::GRAY_COLOR, time_str.str());
1875  } else if (sec < 60s) {
1876  formatted_time_str << span_color("#c80000", time_str.str());
1877  } else if (sec < 120s) {
1878  formatted_time_str << span_color("#c8c800", time_str.str());
1879  } else {
1880  formatted_time_str << time_str.str();
1881  }
1882 
1883  config report;
1885  add_text(report, formatted_time_str.str(),
1886  _("Turn Countdown") + "\n\n" + _("Countdown until your turn automatically ends."));
1887 
1888  return report;
1889 }
1890 
1891 void reports::register_generator(const std::string &name, reports::generator *g)
1892 {
1893  dynamic_generators_[name].reset(g);
1894  all_reports_.clear();
1895 }
1896 
1897 config reports::generate_report(const std::string& name, const reports::context& rc)
1898 {
1899  const auto iter = dynamic_generators_.find(name);
1900  if(iter == dynamic_generators_.end()) {
1901  return generate_builtin_report(name, rc);
1902  }
1903 
1904  return iter->second->generate(rc);
1905 }
1906 
1907 config reports::generate_builtin_report(const std::string& name, const reports::context& rc)
1908 {
1909  const auto iter = static_generators.find(name);
1910  if(iter == static_generators.end()) {
1911  return config();
1912  }
1913 
1914  return std::invoke(iter->second, rc);
1915 }
1916 
1917 const std::set<std::string> &reports::report_list()
1918 {
1919  if (!all_reports_.empty()) return all_reports_;
1920  for (const static_report_generators::value_type &v : static_generators) {
1921  all_reports_.insert(v.first);
1922  }
1923  for (const dynamic_report_generators::value_type &v : dynamic_generators_) {
1924  all_reports_.insert(v.first);
1925  }
1926  return all_reports_;
1927 }
int generic_combat_modifier(int lawful_bonus, unit_alignments::type alignment, bool is_fearless, int max_liminal_bonus)
Returns the amount that a unit's damage should be multiplied by due to a given lawful_bonus.
Definition: attack.cpp:1513
int combat_modifier(const unit_map &units, const gamemap &map, const map_location &loc, unit_alignments::type alignment, bool is_fearless)
Returns the amount that a unit's damage should be multiplied by due to the current time of day.
Definition: attack.cpp:1493
int under_leadership(const unit &u, const map_location &loc)
Tests if the unit at loc is currently affected by leadership.
Definition: attack.cpp:1479
Various functions that implement attacks and attack calculations.
unsigned swarm_blows(unsigned min_blows, unsigned max_blows, unsigned hp, unsigned max_hp)
Calculates the number of blows resulting from swarm.
Definition: attack.hpp:43
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
double g
Definition: astarsearch.cpp:63
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:164
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
config & add_child(std::string_view key)
Definition: config.cpp:436
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
const unit * get_visible_unit(const map_location &loc, const team &current_team, bool see_all=false) const
unit_const_ptr get_visible_unit_shared_ptr(const map_location &loc, const team &current_team, bool see_all=false) const
const team & viewing_team() const
Definition: display.cpp:331
const team & playing_team() const
Definition: display.cpp:326
virtual const map_location & displayed_unit_hex() const
Virtual functions shadowed in game_display.
Definition: display.hpp:213
const map_location & selected_hex() const
Definition: display.hpp:298
bool show_everything() const
Definition: display.hpp:104
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
bool viewing_team_is_playing() const
Definition: display.hpp:122
bool shrouded(const map_location &loc) const
Returns true if location (x,y) is covered in shroud.
Definition: display.cpp:658
std::ostringstream wrapper.
Definition: formatter.hpp:40
const map_location & get_attack_indicator_src()
const pathfind::marked_route & get_route()
Gets the route along which footsteps are drawn to show movement of a unit.
static game_display * get_singleton()
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:265
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
Encapsulates the map of the game.
Definition: map.hpp:176
bool is_village(const map_location &loc) const
Definition: map.cpp:60
std::string get_underlying_terrain_string(const map_location &loc) const
Definition: map.cpp:57
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:53
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:78
static const int UNREACHABLE
Magic value that signifies a hex is unreachable.
Definition: movetype.hpp:174
static prefs & get()
bool use_twelve_hour_clock_format()
const display_context & dc() const
Definition: reports.hpp:58
const unit_map & units() const
Definition: reports.hpp:55
const tod_manager & tod() const
Definition: reports.hpp:60
const gamemap & map() const
Definition: reports.hpp:56
const display & screen() const
Definition: reports.hpp:59
void register_generator(const std::string &name, generator *)
Definition: reports.cpp:1891
std::set< std::string > all_reports_
Definition: reports.hpp:94
config generate_builtin_report(const std::string &name, const context &rc)
Generate the specified report using the given context, excluding dynamic generators.
Definition: reports.cpp:1907
const std::set< std::string > & report_list()
Definition: reports.cpp:1917
std::function< config(const reports::context &)> generator_function
Definition: reports.hpp:88
config generate_report(const std::string &name, const context &ct)
Generate the specified report using the given context.
Definition: reports.cpp:1897
dynamic_report_generators dynamic_generators_
Definition: reports.hpp:96
void set_for_listing(bool for_listing)
Definition: abilities.hpp:281
std::vector< unit_ability_t::tooltip_info > abilities_special_tooltips(const attack_type &at, boost::dynamic_bitset<> &active_list) const
Definition: abilities.cpp:1368
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
Definition: abilities.hpp:270
const std::string & str() const
Definition: tstring.hpp:204
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool uses_shroud() const
Definition: team.hpp:341
const std::string & color() const
Definition: team.hpp:280
const std::string & side_name() const
Definition: team.hpp:331
int side() const
Definition: team.hpp:179
bool is_enemy(int n) const
Definition: team.hpp:267
const std::set< map_location > & villages() const
Definition: team.hpp:175
int gold() const
Definition: team.hpp:180
static t_string get_side_color_name_for_UI(unsigned side)
Definition: team.cpp:982
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:955
bool shrouded(const map_location &loc) const
Definition: team.cpp:636
std::chrono::milliseconds countdown_time() const
Definition: team.hpp:233
const std::string & flag_icon() const
Definition: team.hpp:325
bool fogged(const map_location &loc) const
Definition: team.cpp:645
const t_string & income_description_enemy() const
Definition: terrain.hpp:159
const std::string & icon_image() const
Definition: terrain.hpp:44
const t_string & income_description() const
Definition: terrain.hpp:157
const t_string & income_description_ally() const
Definition: terrain.hpp:158
const std::string & id() const
Definition: terrain.hpp:52
const t_string & description() const
Definition: terrain.hpp:50
const t_translation::ter_list & def_type() const
Definition: terrain.hpp:86
const t_translation::ter_list & union_type() const
Definition: terrain.hpp:87
const t_string & income_description_own() const
Definition: terrain.hpp:160
t_translation::terrain_code number() const
Definition: terrain.hpp:66
int get_max_liminal_bonus() const
const std::vector< time_of_day > & times() const
const time_of_day & get_time_of_day(int for_turn=0) const
Returns global time of day for the passed turn.
Definition: tod_manager.hpp:57
time_of_day get_illuminated_time_of_day(const unit_map &units, const gamemap &map, const map_location &loc, int for_turn=0) const
Returns time of day object for the passed turn at a location.
int get_current_time() const
Definition: tod_manager.hpp:44
const std::string & id() const
Definition: race.hpp:35
const t_string & name(GENDER gender=MALE) const
Definition: race.hpp:37
A single unit type that the player may recruit.
Definition: types.hpp:43
static std::string alignment_description(unit_alignments::type align, unit_race::GENDER gender=unit_race::MALE)
Implementation detail of unit_type::alignment_description.
Definition: types.cpp:779
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:703
This class represents a single unit of a specific type.
Definition: unit.hpp:39
@ selected_hex
Image on the selected unit.
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1031
static std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:101
#define N_(String)
Definition: gettext.hpp:105
static std::string _(const char *str)
Definition: gettext.hpp:97
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2630
std::vector< unit_ability_t::tooltip_info > ability_tooltips() const
Gets the names and descriptions of this unit's abilities.
Definition: abilities.cpp:731
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:427
unit_alignments::type alignment() const
The alignment of this unit.
Definition: unit.hpp:397
int level() const
The current level of this unit.
Definition: unit.hpp:481
const t_string & type_name() const
Gets the translatable name of this unit's type.
Definition: unit.hpp:275
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:421
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1436
std::string small_profile() const
An optional profile image to display in Help.
Definition: unit.cpp:1168
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1954
const unit_race * race() const
Gets this unit's race.
Definition: unit.hpp:415
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:261
int experience() const
The current number of experience points this unit has.
Definition: unit.hpp:445
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:286
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
std::vector< t_string > unit_special_notes() const
The unit's special notes.
Definition: unit.cpp:2883
int max_experience() const
The max number of experience points this unit can have.
Definition: unit.hpp:451
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:387
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:309
t_string unit_description() const
A detailed description of this unit.
Definition: unit.hpp:372
@ STATE_SLOWED
Definition: unit.hpp:775
@ STATE_INVULNERABLE
The unit is a guardian - it won't move unless a target is sighted.
Definition: unit.hpp:782
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:777
@ STATE_UNHEALABLE
The unit has not moved.
Definition: unit.hpp:780
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:776
std::vector< std::pair< std::string, std::string > > amla_icons() const
Gets the image and description data for modification advancements.
Definition: unit.cpp:1866
bool can_advance() const
Checks whether this unit has any options to advance to.
Definition: unit.hpp:178
std::map< std::string, std::string > advancement_icons() const
Gets and image path and and associated description for each advancement option.
Definition: unit.cpp:1826
int resistance_against(const std::string &damage_name, bool attacker, const map_location &loc) const
The unit's resistance against a given damage type.
Definition: unit.cpp:1817
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:848
int defense_modifier(const t_translation::terrain_code &terrain, const map_location &loc) const
The unit's defense on a given terrain.
Definition: unit.cpp:1767
utils::string_map_res get_base_resistances() const
Gets resistances without any abilities applied.
Definition: unit.hpp:956
int max_attacks() const
The maximum number of attacks this unit may perform per turn, usually 1.
Definition: unit.hpp:884
int attacks_left() const
Gets the remaining number of attacks this unit can perform this turn.
Definition: unit.hpp:900
color_t xp_color() const
Color for this unit's XP.
Definition: unit.cpp:1256
color_t hp_color() const
Color for this unit's current hitpoints.
Definition: unit.cpp:1212
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2788
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2607
int jamming() const
Gets the unit's jamming points.
Definition: unit.hpp:1376
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit's movement cost on a particular terrain.
Definition: unit.hpp:1410
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1252
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1236
int vision() const
Gets the unit's vision points.
Definition: unit.hpp:1370
std::vector< std::string > trait_nonhidden_ids() const
Gets the ids of the traits corresponding to those returned by trait_names() and trait_descriptions().
Definition: unit.hpp:1011
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1186
const std::vector< t_string > & trait_descriptions() const
Gets the descriptions of the currently registered traits.
Definition: unit.hpp:1000
const std::vector< t_string > & trait_names() const
Gets the names of the currently registered traits.
Definition: unit.hpp:990
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:203
auto string_table
Definition: language.hpp:68
constexpr int round_damage(double base_damage, int bonus, int divisor)
round (base_damage * bonus / divisor) to the closest integer, but up or down towards base_damage
Definition: math.hpp:78
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition: chrono.hpp:62
double get_battery_percentage()
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:189
const std::string weapon_details_sep
Definition: constants.cpp:50
const color_t BAD_COLOR
const color_t GRAY_COLOR
std::string escape_text(std::string_view text)
Escapes the pango markup characters in a text.
Definition: escape.hpp:33
const std::string unicode_bullet
Definition: constants.cpp:47
const color_t weapon_details_color
const color_t good_dmg_color
const std::string unicode_en_dash
Definition: constants.cpp:43
const color_t weapon_color
const std::string unicode_figure_dash
Definition: constants.cpp:45
const color_t INACTIVE_COLOR
const std::string weapon_numbers_sep
Definition: constants.cpp:49
const std::string unicode_minus
Definition: constants.cpp:42
const color_t bad_dmg_color
std::string tod_bright
std::string time_icon
std::string observer
std::string flag_icon
std::string battery_icon
std::string tod_dark
std::string path
Definition: filesystem.cpp:106
std::string flag_rgb
color_t blue_to_white(double val, bool for_text)
const bool & debug
Definition: game_config.cpp:95
color_t red_to_green(double val, bool for_text)
Return a color corresponding to the value val red for val=0.0 to green for val=100....
std::string get_unit_type_help_id(const unit_type &t)
Given a unit type, find the corresponding help topic's id.
Definition: help.cpp:37
Functions to load and save images from/to disk.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:851
logger & info()
Definition: log.cpp:351
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:176
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition: markup.hpp:161
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
std::string help_to_pango_markup(const std::string &help_markup)
Definition: markup.cpp:525
static std::string at(const std::string &file, int line)
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
const terrain_code MINUS
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
const ter_match ALL_OFF_MAP
const terrain_code PLUS
const terrain_code FOGGED
static std::string gettext(const char *str)
Definition: gettext.hpp:62
static std::string unit_level_tooltip(const int level, const std::vector< std::string > &adv_to_types, const std::vector< config > &adv_to_mods)
Definition: helper.cpp:60
std::string resistance_color(const int resistance)
Maps resistance <= -60 (resistance value <= -60%) to intense red.
Definition: helper.cpp:54
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
constexpr auto reverse
Definition: ranges.hpp:44
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::string signed_value(int val)
Convert into a signed value (using the Unicode "−" and +0 convention.
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
@ enemy
Belongs to a non-friendly side; normally visualised by not displaying an orb.
This module contains various pathfinding functions and utilities.
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
static config unit_name(const unit *u)
Definition: reports.cpp:163
static config image_report(const std::string &image, const std::string &tooltip="", const std::string &help="")
Definition: reports.cpp:79
static std::string flush(std::ostringstream &s)
Definition: reports.cpp:97
static void add_image(config &report, const std::string &image, const std::string &tooltip, const std::string &help="")
Definition: reports.cpp:62
static config unit_hp(const reports::context &rc, const unit *u)
Definition: reports.cpp:479
static config unit_type(const unit *u)
Definition: reports.cpp:191
static std::string side_tooltip(const team &team)
Definition: reports.cpp:238
static config unit_jamming(const unit *u)
Definition: reports.cpp:662
static const unit * get_visible_unit(const reports::context &rc)
Definition: reports.cpp:134
static config tod_stats_at(const reports::context &rc, const map_location &hex)
Definition: reports.cpp:1348
static unit_const_ptr get_selected_unit_ptr(const reports::context &rc)
Definition: reports.cpp:148
static int attack_info(const reports::context &rc, const attack_type &at, config &res, const unit &u, const map_location &hex, const unit *sec_u=nullptr, const const_attack_ptr &sec_u_weapon=nullptr)
Definition: reports.cpp:788
std::map< std::string, reports::generator_function > static_report_generators
Definition: reports.cpp:118
static config unit_side(const reports::context &rc, const unit *u)
Definition: reports.cpp:248
static config unit_defense(const reports::context &rc, const unit *u, const map_location &displayed_unit_hex)
Definition: reports.cpp:573
static std::string format_prob(double prob)
Definition: reports.cpp:1111
static config unit_status(const reports::context &rc, const unit *u)
Definition: reports.cpp:338
static config gray_inactive(const reports::context &rc, const std::string &str, const std::string &tooltip="")
Definition: reports.cpp:155
static static_report_generators static_generators
Definition: reports.cpp:119
static config unit_level(const unit *u)
Definition: reports.cpp:280
static config unit_vision(const unit *u)
Definition: reports.cpp:639
static const color_t attack_info_percent_color(int resistance)
Maps resistance <= -60 (resistance value <= -60%) to intense red.
Definition: reports.cpp:782
static config unit_traits(const unit *u)
Definition: reports.cpp:308
static config unit_abilities_report(const unit *u, const map_location &loc)
Definition: reports.cpp:417
static config unit_box_at(const reports::context &rc, const map_location &mouseover_hex)
Definition: reports.cpp:1441
static const time_of_day get_visible_time_of_day_at(const reports::context &rc, const map_location &hex)
Definition: reports.cpp:104
static config unit_race(const unit *u)
Definition: reports.cpp:219
static void add_text(config &report, const std::string &text, const std::string &tooltip, const std::string &help="")
Definition: reports.cpp:53
static config time_of_day_at(const reports::context &rc, const map_location &mouseover_hex)
Definition: reports.cpp:1389
static config unit_weapons(const reports::context &rc, const unit_const_ptr &attacker, const map_location &attacker_pos, const unit *defender, bool show_attacker)
Definition: reports.cpp:1130
#define REPORT_GENERATOR(n, cn)
Definition: reports.cpp:129
static config text_report(const std::string &text, const std::string &tooltip="", const std::string &help="")
Definition: reports.cpp:71
static config unit_moves(const reports::context &rc, const unit *u, bool is_visible_unit)
Definition: reports.cpp:680
static config unit_alignment(const reports::context &rc, const unit *u, const map_location &hex)
Definition: reports.cpp:380
static void add_status(config &r, const std::string &path, char const *desc1, char const *desc2)
Definition: reports.cpp:89
static const unit * get_selected_unit(const reports::context &rc)
Definition: reports.cpp:141
static std::string format_hp(unsigned hp)
Definition: reports.cpp:1123
static config unit_advancement_options(const unit *u)
Definition: reports.cpp:553
static config unit_xp(const unit *u)
Definition: reports.cpp:528
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:54
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition: attack.hpp:80
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition: attack.hpp:55
int damage
Effective damage of the weapon (all factors accounted for).
Definition: attack.hpp:76
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:75
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
All combat-related info.
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
Encapsulates the map of the game.
Definition: location.hpp:46
bool valid() const
Definition: location.hpp:111
static const map_location & null_location()
Definition: location.hpp:103
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::vector< map_location > & steps
Definition: pathfind.hpp:187
report_generator_helper(const char *name, const reports::generator_function &g)
Definition: reports.cpp:123
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
int bonus_modified
Definition: time_of_day.hpp:84
std::string id
Definition: time_of_day.hpp:90
tod_color color
The color modifications that should be made to the game board to reflect the time of day.
int lawful_bonus
The % bonus lawful units receive.
Definition: time_of_day.hpp:83
t_string name
Definition: time_of_day.hpp:88
std::string image
The image to be displayed in the game status.
Definition: time_of_day.hpp:87
static int get_acceleration()
Definition: types.cpp:534
mock_char c
static map_location::direction s
#define b