The Battle for Wesnoth  1.19.20+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:") << " " << markup::bold(u->type_name()) << "\n"
197  << u->unit_description();
198  if(const auto& notes = u->unit_special_notes(); !notes.empty()) {
199  tooltip << "\n\n" << _("Special Notes:") << '\n';
200  for(const auto& note : notes) {
201  tooltip << font::unicode_bullet << " " << note << '\n';
202  }
203  }
204 
205  return text_report(u->type_name(), tooltip.str(), help::get_unit_type_help_id(u->type()));
206 }
208 {
209  const unit *u = get_visible_unit(rc);
210  return unit_type(u);
211 }
212 REPORT_GENERATOR(selected_unit_type, rc)
213 {
214  const unit *u = get_selected_unit(rc);
215  return unit_type(u);
216 }
217 
218 static config unit_race(const unit* u)
219 {
220  if (!u) return config();
221  std::ostringstream str, tooltip;
222  str << u->race()->name(u->gender());
223  tooltip << _("Race:") << " " << markup::bold(u->race()->name(u->gender()));
224  return text_report(str.str(), tooltip.str(), "..race_" + u->race()->id());
225 }
227 {
228  const unit *u = get_visible_unit(rc);
229  return unit_race(u);
230 }
231 REPORT_GENERATOR(selected_unit_race, rc)
232 {
233  const unit *u = get_selected_unit(rc);
234  return unit_race(u);
235 }
236 
237 static std::string side_tooltip(const team& team)
238 {
239  if(team.side_name().empty())
240  return "";
241 
242  return VGETTEXT("Side: <b>$side_name</b> ($color_name)",
243  {{"side_name", team.side_name()},
244  {"color_name", team::get_side_color_name_for_UI(team.side()) }});
245 }
246 
247 static config unit_side(const reports::context& rc, const unit* u)
248 {
249  if (!u) return config();
250 
251  config report;
252  const team &u_team = rc.dc().get_team(u->side());
253  std::string flag_icon = u_team.flag_icon();
254  std::string old_rgb = game_config::flag_rgb;
255  std::string new_rgb = u_team.color();
256  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
257  if (flag_icon.empty())
259 
260  std::stringstream text;
261  text << " " << u->side();
262 
263  const std::string& tooltip = side_tooltip(u_team);
264  add_image(report, flag_icon + mods, tooltip, "");
265  add_text(report, text.str(), tooltip, "");
266  return report;
267 }
269 {
270  const unit *u = get_visible_unit(rc);
271  return unit_side(rc,u);
272 }
273 REPORT_GENERATOR(selected_unit_side, rc)
274 {
275  const unit *u = get_selected_unit(rc);
276  return unit_side(rc, u);
277 }
278 
279 static config unit_level(const unit* u)
280 {
281  if (!u) return config();
282  return text_report(std::to_string(u->level()), unit_helper::unit_level_tooltip(*u));
283 }
285 {
286  const unit *u = get_visible_unit(rc);
287  return unit_level(u);
288 }
289 REPORT_GENERATOR(selected_unit_level, rc)
290 {
291  const unit *u = get_selected_unit(rc);
292  return unit_level(u);
293 }
294 
295 REPORT_GENERATOR(unit_amla, rc)
296 {
297  const unit *u = get_visible_unit(rc);
298  if (!u) return config();
299  config res;
300  typedef std::pair<std::string, std::string> pair_string;
301  for (const pair_string &ps : u->amla_icons()) {
302  add_image(res, ps.first, ps.second);
303  }
304  return res;
305 }
306 
307 static config unit_traits(const unit* u)
308 {
309  if (!u) return config();
310  config res;
311  const std::vector<t_string> &traits = u->trait_names();
312  const std::vector<t_string> &descriptions = u->trait_descriptions();
313  const std::vector<std::string> &trait_ids = u->trait_nonhidden_ids();
314  unsigned nb = traits.size();
315  for (unsigned i = 0; i < nb; ++i)
316  {
317  std::ostringstream str, tooltip;
318  str << traits[i];
319  if (i != nb - 1 ) str << ", ";
320  tooltip << _("Trait:") << " " << markup::bold(traits[i]) << "\n"
321  << descriptions[i];
322  add_text(res, str.str(), tooltip.str(), "traits_" + trait_ids[i]);
323  }
324  return res;
325 }
327 {
328  const unit *u = get_visible_unit(rc);
329  return unit_traits(u);
330 }
331 REPORT_GENERATOR(selected_unit_traits, rc)
332 {
333  const unit *u = get_selected_unit(rc);
334  return unit_traits(u);
335 }
336 
337 static config unit_status(const reports::context& rc, const unit* u)
338 {
339  if (!u) return config();
340  config res;
341  map_location displayed_unit_hex = rc.screen().displayed_unit_hex();
342  if (rc.map().on_board(displayed_unit_hex) && u->invisible(displayed_unit_hex)) {
343  add_status(res, "misc/invisible.png", N_("invisible:"),
344  N_("This unit is invisible. It cannot be seen or attacked by enemy units."));
345  }
346  if (u->get_state(unit::STATE_SLOWED)) {
347  add_status(res, "misc/slowed.png", N_("slowed:"),
348  N_("This unit has been slowed. It will only deal half its normal damage when attacking and its movement cost is doubled."));
349  }
351  add_status(res, "misc/poisoned.png", N_("poisoned:"),
352  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."));
353  }
355  add_status(res, "misc/petrified.png", N_("petrified:"),
356  N_("This unit has been petrified. It may not move or attack."));
357  }
359  add_status(res, "misc/unhealable.png", N_("unhealable:"),
360  N_("This unit is unhealable. It cannot be healed by healers or villages and doesn’t benefit from resting."));
361  }
363  add_status(res, "misc/invulnerable.png", N_("invulnerable:"),
364  N_("This unit is invulnerable. It cannot be harmed by any attack."));
365  }
366  return res;
367 }
369 {
370  const unit *u = get_visible_unit(rc);
371  return unit_status(rc,u);
372 }
373 REPORT_GENERATOR(selected_unit_status, rc)
374 {
375  const unit *u = get_selected_unit(rc);
376  return unit_status(rc, u);
377 }
378 
379 static config unit_alignment(const reports::context& rc, const unit* u, const map_location& hex)
380 {
381  if (!u) return config();
382  std::ostringstream str, tooltip;
383  const std::string align = unit_type::alignment_description(u->alignment(), u->gender());
384  const std::string align_id = unit_alignments::get_string(u->alignment());
385  const time_of_day effective_tod = get_visible_time_of_day_at(rc, hex);
386  int cm = combat_modifier(effective_tod, u->alignment(), u->is_fearless());
387 
388  color_t color = font::weapon_color;
389  if (cm != 0)
390  color = (cm > 0) ? font::good_dmg_color : font::bad_dmg_color;
391 
392  str << align << " (" << span_color(color, utils::signed_percent(cm)) << ")";
393 
394  tooltip << _("Alignment:") << " " << markup::bold(align) << "\n"
395  << string_table[align_id + "_description"];
396 
397  return text_report(str.str(), tooltip.str(), "time_of_day");
398 }
400 {
401  const unit *u = get_visible_unit(rc);
402  const map_location& mouseover_hex = rc.screen().mouseover_hex();
403  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
404  const map_location& hex = mouseover_hex.valid() ? mouseover_hex : displayed_unit_hex;
405  return unit_alignment(rc, u, hex);
406 }
407 REPORT_GENERATOR(selected_unit_alignment, rc)
408 {
409  const unit *u = get_selected_unit(rc);
410  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
411  const map_location& hex_to_show_alignment_at =
412  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
413  return unit_alignment(rc, u, hex_to_show_alignment_at);
414 }
415 
417 {
418  if (!u) return config();
419  config res;
420 
421  boost::dynamic_bitset<> active;
422  const auto abilities = u->ability_tooltips(active, loc);
423 
424  const std::size_t abilities_size = abilities.size();
425  for(std::size_t i = 0; i != abilities_size; ++i) {
426  // Aliases for readability:
427  const auto& [display_name, description, help_id] = abilities[i];
428 
429  std::ostringstream str, tooltip;
430 
431  if(active[i]) {
432  str << display_name;
433  } else {
434  str << span_color(font::INACTIVE_COLOR, display_name);
435  }
436 
437  if(i + 1 != abilities_size) {
438  str << ", ";
439  }
440 
441  tooltip << _("Ability:") << " " << markup::bold(display_name);
442  if(!active[i]) {
443  tooltip << markup::italic(_(" (inactive)"));
444  }
445 
446  tooltip << '\n' << description;
447 
448  add_text(res, str.str(), tooltip.str(), "ability_" + help_id);
449  }
450 
451  return res;
452 }
454 {
455  const unit *u = get_visible_unit(rc);
456  const team &viewing_team = rc.screen().viewing_team();
457  const map_location& mouseover_hex = rc.screen().mouseover_hex();
458  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
459  const map_location& hex = (mouseover_hex.valid() && !viewing_team.shrouded(mouseover_hex)) ? mouseover_hex : displayed_unit_hex;
460 
461  return unit_abilities_report(u, hex);
462 }
463 REPORT_GENERATOR(selected_unit_abilities, rc)
464 {
465  const unit *u = get_selected_unit(rc);
466 
467  const map_location& mouseover_hex = rc.screen().mouseover_hex();
468  const unit *visible_unit = get_visible_unit(rc);
469  const team &viewing_team = rc.screen().viewing_team();
470 
471  if (visible_unit && u && visible_unit->id() != u->id() && mouseover_hex.valid() && !viewing_team.shrouded(mouseover_hex))
472  return unit_abilities_report(u, mouseover_hex);
473  else
474  return unit_abilities_report(u, u->get_location());
475 }
476 
477 
478 static config unit_hp(const reports::context& rc, const unit* u)
479 {
480  if (!u) return config();
481  std::ostringstream str, tooltip;
482  str << span_color(u->hp_color(), u->hitpoints(), '/', u->max_hitpoints());
483 
484  std::vector<std::string> resistances_table;
485 
486  bool att_def_diff = false;
487  map_location displayed_unit_hex = rc.screen().displayed_unit_hex();
488  for (const utils::string_map_res::value_type &resist : u->get_base_resistances())
489  {
490  std::ostringstream line;
491  line << translation::gettext(resist.first.c_str()) << ": ";
492  // Some units have different resistances when attacking or defending.
493  int res_att = 100 - u->resistance_against(resist.first, true, displayed_unit_hex);
494  int res_def = 100 - u->resistance_against(resist.first, false, displayed_unit_hex);
495  const std::string def_color = unit_helper::resistance_color(res_def);
496  if (res_att == res_def) {
497  line << span_color(def_color, utils::signed_percent(res_def)) << '\n';
498  } else {
499  const std::string att_color = unit_helper::resistance_color(res_att);
500  line << span_color(att_color, utils::signed_percent(res_att)) << "/";
501  line << span_color(def_color, utils::signed_percent(res_def)) << '\n';
502  att_def_diff = true;
503  }
504  resistances_table.push_back(line.str());
505  }
506 
507  tooltip << _("Resistances:") << " ";
508  if (att_def_diff)
509  tooltip << _("(Att / Def)");
510  tooltip << '\n';
511  for (const std::string &line : resistances_table) {
512  tooltip << line;
513  }
514  return text_report(str.str(), tooltip.str());
515 }
517 {
518  const unit *u = get_visible_unit(rc);
519  return unit_hp(rc, u);
520 }
521 REPORT_GENERATOR(selected_unit_hp, rc)
522 {
523  const unit *u = get_selected_unit(rc);
524  return unit_hp(rc, u);
525 }
526 
527 static config unit_xp(const unit* u)
528 {
529  if (!u) return config();
530  std::ostringstream str, tooltip;
531  if(u->can_advance()) {
532  str << span_color(u->xp_color(), u->experience(), '/', u->max_experience());
533  } else {
535  }
536 
538  tooltip << _("Experience Modifier:") << " " << exp_mod << '%';
539  return text_report(str.str(), tooltip.str());
540 }
542 {
543  const unit *u = get_visible_unit(rc);
544  return unit_xp(u);
545 }
546 REPORT_GENERATOR(selected_unit_xp, rc)
547 {
548  const unit *u = get_selected_unit(rc);
549  return unit_xp(u);
550 }
551 
553 {
554  if (!u) return config();
555  config res;
556  for (const auto& ps : u->advancement_icons()) {
557  add_image(res, ps.first, ps.second);
558  }
559  return res;
560 }
562 {
563  const unit *u = get_visible_unit(rc);
564  return unit_advancement_options(u);
565 }
566 REPORT_GENERATOR(selected_unit_advancement_options, rc)
567 {
568  const unit *u = get_selected_unit(rc);
569  return unit_advancement_options(u);
570 }
571 
572 static config unit_defense(const reports::context& rc, const unit* u, const map_location& displayed_unit_hex)
573 {
574  if(!u) {
575  return config();
576  }
577 
578  std::ostringstream str, tooltip;
579  const gamemap &map = rc.map();
580  if(!rc.map().on_board(displayed_unit_hex)) {
581  return config();
582  }
583 
584  const terrain_type& terrain = map.get_terrain_info(displayed_unit_hex);
585  int def = 100 - u->defense_modifier(terrain.number(), displayed_unit_hex);
586  color_t color = game_config::red_to_green(def);
587  str << span_color(color, def, '%');
588  tooltip << _("Terrain:") << " " << markup::bold(terrain.description()) << "\n";
589 
590  const t_translation::ter_list& underlyings = terrain.def_type();
591  if (underlyings.size() != 1 || underlyings.front() != terrain.number())
592  {
593  bool revert = false;
594  for (const t_translation::terrain_code &t : underlyings)
595  {
596  if (t == t_translation::MINUS) {
597  revert = true;
598  } else if (t == t_translation::PLUS) {
599  revert = false;
600  } else {
601  int t_def = 100 - u->defense_modifier(t);
602  color_t t_color = game_config::red_to_green(t_def);
603  tooltip << '\t' << map.get_terrain_info(t).description() << ": "
604  << span_color(t_color, t_def, '%')
605  << (revert ? _("maximum^max.") : _("minimum^min.")) << '\n';
606  }
607  }
608  }
609 
610  tooltip << markup::bold(_("Defense:")) << " " << span_color(color, def, '%');
611  const std::string has_variations_prefix = (u->type().show_variations_in_help() ? ".." : "");
612  return text_report(str.str(), tooltip.str(), has_variations_prefix + "unit_" + u->type_id());
613 }
615 {
616  const unit *u = get_visible_unit(rc);
617  const team &viewing_team = rc.screen().viewing_team();
618  const map_location& mouseover_hex = rc.screen().mouseover_hex();
619  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
620  const map_location& hex = (mouseover_hex.valid() && !viewing_team.shrouded(mouseover_hex)) ? mouseover_hex : displayed_unit_hex;
621  return unit_defense(rc, u, hex);
622 }
623 REPORT_GENERATOR(selected_unit_defense, rc)
624 {
625  const unit *u = get_selected_unit(rc);
626  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
627  if(attack_indicator_src.valid())
628  return unit_defense(rc, u, attack_indicator_src);
629 
630  const map_location& mouseover_hex = rc.screen().mouseover_hex();
631  const unit *visible_unit = get_visible_unit(rc);
632  if(visible_unit && u && visible_unit->id() != u->id() && mouseover_hex.valid())
633  return unit_defense(rc, u, mouseover_hex);
634  else
635  return unit_defense(rc, u, u->get_location());
636 }
637 
638 static config unit_vision(const unit* u)
639 {
640  if (!u) return config();
641 
642  std::ostringstream str, tooltip;
643  if (u->vision() != u->total_movement()) {
644  // TRANSLATORS: single-letter abbreviation of "vision", fits in a small space on the sidebar
645  str << _("vision^V:") << ' ' << u->vision();
646  tooltip << _("vision:") << ' ' << u->vision() << '\n';
647  }
648  return text_report(str.str(), tooltip.str());
649 }
651 {
652  const unit* u = get_visible_unit(rc);
653  return unit_vision(u);
654 }
655 REPORT_GENERATOR(selected_unit_vision, rc)
656 {
657  const unit* u = get_selected_unit(rc);
658  return unit_vision(u);
659 }
660 
661 static config unit_jamming(const unit* u)
662 {
663  if(!u) return config();
664 
665  std::ostringstream str, tooltip;
666  if(u->jamming() != 0) {
667  // TRANSLATORS: single-letter abbreviation of "jamming", fits in a small space on the sidebar
668  str << _("jamming^J:") << ' ' << u->jamming();
669  tooltip << _("jamming:") << ' ' << u->jamming() << '\n';
670  }
671  return text_report(str.str(), tooltip.str());
672 }
674 {
675  const unit* u = get_visible_unit(rc);
676  return unit_jamming(u);
677 }
678 
679 static config unit_moves(const reports::context& rc, const unit* u, bool is_visible_unit)
680 {
681  if (!u) return config();
682  std::ostringstream str, tooltip;
683  double movement_frac = 1.0;
684 
685  std::set<terrain_movement> terrain_moves;
686 
687  if (u->side() == rc.screen().playing_team().side()) {
688  movement_frac = static_cast<double>(u->movement_left()) / std::max<int>(1, u->total_movement());
689  if (movement_frac > 1.0)
690  movement_frac = 1.0;
691  }
692 
693  tooltip << _("Movement Costs:") << "\n";
694  for (t_translation::terrain_code terrain : prefs::get().encountered_terrains()) {
696  continue;
697 
698  const terrain_type& info = rc.map().get_terrain_info(terrain);
699 
700  if (info.union_type().size() == 1 && info.union_type()[0] == info.number() && info.is_nonnull()) {
701  terrain_moves.emplace(info.name(), u->movement_cost(terrain));
702  }
703  }
704 
705  for (const terrain_movement& tm : terrain_moves) {
706  tooltip << tm.name << ": ";
707 
708  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
709  const bool cannot_move = tm.moves > u->total_movement(); // cannot move in this terrain
710  double movement_red_to_green = 100.0 - 25.0 * tm.moves;
711 
712  std::stringstream temp_str;
713  // A 5 MP margin; if the movement costs go above
714  // the unit's max moves + 5, we replace it with dashes.
715  if (cannot_move && (tm.moves > u->total_movement() + 5)) {
716  temp_str << font::unicode_figure_dash;
717  } else if (cannot_move) {
718  temp_str << "(" << tm.moves << ")";
719  } else {
720  temp_str << tm.moves;
721  }
722  if (tm.moves != 0) {
723  const int movement_hexes_per_turn = u->total_movement() / tm.moves;
724  temp_str << " ";
725  for (int i = 0; i < movement_hexes_per_turn; ++i) {
726  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
727  temp_str << "\u2b23\u200b";
728  }
729  }
730 
731  // passing true to select the less saturated red-to-green scale
732  color_t color = game_config::red_to_green(movement_red_to_green, true);
733  tooltip << span_color(color, temp_str.str()) << '\n';
734 
735  }
736 
737  int grey = 128 + static_cast<int>((255 - 128) * movement_frac);
738  color_t c = color_t(grey, grey, grey);
739  int numerator = u->movement_left();
740  if(is_visible_unit) {
742  if(route.steps.size() > 0) {
743  numerator -= route.move_cost;
744  if(numerator < 0) {
745  // Multi-turn move
746  // TODO: what to show in this case?
747  numerator = 0;
748  }
749 
750  // If the route captures a village, assume that uses up all remaining MP.
751  const auto& end = route.marks.find(route.steps.back());
752  if(end != route.marks.end() && end->second.capture) {
753  numerator = 0;
754  }
755  } else {
756  // TODO: if the mouseover hex is unreachable (for example, a deep water hex
757  // and the current unit is a land unit), what to show?
758  }
759  }
760  str << span_color(c, numerator, '/', u->total_movement());
761  return text_report(str.str(), tooltip.str());
762 }
764 {
765  const unit *u = get_visible_unit(rc);
766  return unit_moves(rc, u, true);
767 }
768 REPORT_GENERATOR(selected_unit_moves, rc)
769 {
770  const unit *u = get_selected_unit(rc);
771  return unit_moves(rc, u, false);
772 }
773 
774 /**
775  * Maps resistance <= -60 (resistance value <= -60%) to intense red.
776  * Maps resistance >= 60 (resistance value >= 60%) to intense green.
777  * Intermediate values are affinely mapped to the red-to-green scale,
778  * with 0 (0%) being mapped to yellow.
779  * Compare unit_helper::resistance_color().
780  */
781 static inline const color_t attack_info_percent_color(int resistance)
782 {
783  // Passing false to select the more saturated red-to-green scale.
784  return game_config::red_to_green(50.0 + resistance * 5.0 / 6.0, false);
785 }
786 
787 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)
788 {
789  std::ostringstream str, tooltip;
790  int damage = 0;
791  const bool attacking = (u.side() == rc.screen().playing_team().side());
792 
793  struct string_with_tooltip {
794  std::string str;
795  std::string tooltip;
796  };
797 
798  {
799  auto ctx = specials_context_t::make({ u.shared_from_this(), hex, at.shared_from_this() }, { }, attacking);
800  int base_damage = at.damage();
801  double specials_damage = at.modified_damage();
802  int damage_multiplier = 100;
803  const_attack_ptr weapon = at.shared_from_this();
804  unit_alignments::type attack_alignment = weapon->alignment().value_or(u.alignment());
805  int tod_bonus = combat_modifier(get_visible_time_of_day_at(rc, hex), attack_alignment, u.is_fearless());
806  damage_multiplier += tod_bonus;
807  int leader_bonus = under_leadership(u, ctx);
808  if (leader_bonus != 0)
809  damage_multiplier += leader_bonus;
810 
812  int damage_divisor = slowed ? 20000 : 10000;
813  // Assume no specific resistance (i.e. multiply by 100).
814  damage = round_damage(specials_damage, damage_multiplier * 100, damage_divisor);
815 
816  // Hit points are used to calculate swarm, so they need to be bounded.
817  unsigned max_hp = u.max_hitpoints();
818  unsigned cur_hp = std::min<unsigned>(std::max(0, u.hitpoints()), max_hp);
819 
820  unsigned base_attacks = at.num_attacks();
821  unsigned min_attacks, max_attacks;
822  at.modified_attacks(min_attacks, max_attacks);
823  unsigned num_attacks = swarm_blows(min_attacks, max_attacks, cur_hp, max_hp);
824 
825  color_t dmg_color = font::weapon_color;
826  if (damage > std::round(specials_damage)) {
827  dmg_color = font::good_dmg_color;
828  } else if (damage < std::round(specials_damage)) {
829  dmg_color = font::bad_dmg_color;
830  }
831 
832  str << span_color(dmg_color, " ", damage)
833  << span_color(font::weapon_color, font::weapon_numbers_sep, num_attacks, ' ', at.name())
834  << "\n";
835  tooltip << _("Weapon:") << " " << markup::bold(at.name()) << "\n"
836  << _("Damage:") << " " << markup::bold(damage) << "\n";
837 
838  if ( tod_bonus || leader_bonus || slowed || specials_damage != base_damage )
839  {
840  tooltip << '\t' << _("Base damage:") << " " << base_damage << '\n';
841  if ( specials_damage != base_damage ) {
842  tooltip << '\t' << _("With specials:") << " " << specials_damage << '\n';
843  }
844  if (tod_bonus) {
845  tooltip << '\t' << _("Time of day:") << " "
846  << utils::signed_percent(tod_bonus) << '\n';
847  }
848  if (leader_bonus) {
849  tooltip << '\t' << _("Leadership:") << " "
850  << utils::signed_percent(leader_bonus) << '\n';
851  }
852  if (slowed) {
853  tooltip << '\t' << _("Slowed:") << " " << "/ 2" << '\n';
854  }
855  }
856 
857  tooltip << _("Attacks:") << " " << markup::bold(num_attacks) << "\n";
858  if ( max_attacks != min_attacks && cur_hp != max_hp ) {
859  if ( max_attacks < min_attacks ) {
860  // "Reverse swarm"
861  tooltip << '\t' << _("Max swarm bonus:") << " " << (min_attacks-max_attacks) << '\n';
862  tooltip << '\t' << _("Swarm:") << " " << "* "<< (100 - cur_hp*100/max_hp) << "%\n";
863  tooltip << '\t' << _("Base attacks:") << " " << '+' << base_attacks << '\n';
864  // The specials line will not necessarily match up with how the
865  // specials are calculated, but for an unusual case, simple brevity
866  // trumps complexities.
867  if ( max_attacks != base_attacks ) {
868  int attack_diff = static_cast<int>(max_attacks) - static_cast<int>(base_attacks);
869  tooltip << '\t' << _("Specials:") << " " << utils::signed_value(attack_diff) << '\n';
870  }
871  }
872  else {
873  // Regular swarm
874  tooltip << '\t' << _("Base attacks:") << " " << base_attacks << '\n';
875  if ( max_attacks != base_attacks ) {
876  tooltip << '\t' << _("With specials:") << " " << max_attacks << '\n';
877  }
878  if ( min_attacks != 0 ) {
879  tooltip << '\t' << _("Subject to swarm:") << " " << (max_attacks-min_attacks) << '\n';
880  }
881  tooltip << '\t' << _("Swarm:") << " " << "* "<< (cur_hp*100/max_hp) << "%\n";
882  }
883  }
884  else if ( num_attacks != base_attacks ) {
885  tooltip << '\t' << _("Base attacks:") << " " << base_attacks << '\n';
886  tooltip << '\t' << _("With specials:") << " " << num_attacks << '\n';
887  }
888 
889  const string_with_tooltip damage_and_num_attacks {flush(str), flush(tooltip)};
890 
891  std::string range = string_table["range_" + at.range()];
892  std::pair<std::string, std::set<std::string>> all_damage_types = at.damage_types();
893  std::string type = all_damage_types.first;
894  std::set<std::string> alt_types = all_damage_types.second;
895  std::string lang_type = string_table["type_" + type];
896  for(auto alt_t : alt_types){
897  lang_type += ", " + string_table["type_" + alt_t];
898  }
899 
900  // SCALE_INTO() is needed in case the 72x72 images/misc/missing-image.png is substituted.
901  const std::string range_png = std::string("icons/profiles/") + at.range() + "_attack.png~SCALE_INTO(16,16)";
902  const std::string type_png = std::string("icons/profiles/") + type + ".png~SCALE_INTO(16,16)";
903  std::vector<std::string> secondary_types_png;
904  for(const auto& alt_t : alt_types) {
905  secondary_types_png.push_back(std::string("icons/profiles/") + alt_t + ".png~SCALE_INTO(16,16)");
906  }
907 
908  // If any of the images is missing, then add a text description too.
909  bool all_pngs_exist = image::exists(range_png);
910  all_pngs_exist &= image::exists(type_png);
911  for(const auto& png : secondary_types_png) {
912  all_pngs_exist &= image::exists(png);
913  }
914  if(!all_pngs_exist) {
915  str << span_color(font::weapon_details_color, " ", " ", range, font::weapon_details_sep, lang_type)
916  << "\n";
917  }
918 
919  tooltip << _("Weapon range:") << " " << markup::bold(range) << "\n"
920  << _("Damage type:") << " " << markup::bold(lang_type) << "\n"
921  << _("Damage versus:") << " " << '\n';
922 
923  // Show this weapon damage and resistance against all the different units.
924  // We want weak resistances (= good damage) first.
925  std::map<int, std::set<std::string>, std::greater<int>> resistances;
926  std::set<std::string> seen_types;
927  const team &unit_team = rc.dc().get_team(u.side());
928  const team &viewing_team = rc.screen().viewing_team();
929  for (const unit &enemy : rc.units())
930  {
931  if (enemy.incapacitated()) //we can't attack statues so don't display them in this tooltip
932  continue;
933  if (!unit_team.is_enemy(enemy.side()))
934  continue;
935  const map_location &loc = enemy.get_location();
936  const bool see_all = game_config::debug || rc.screen().show_everything();
937  if (!enemy.is_visible_to_team(viewing_team, see_all))
938  continue;
939  bool new_type = seen_types.insert(enemy.type_id()).second;
940  if (new_type) {
941  auto ctx = specials_context_t::make(
942  { u.shared_from_this(), hex, at.shared_from_this() },
943  { enemy.shared_from_this(), loc, nullptr },
944  attacking);
945  const auto [damage_type, resistance] = weapon->effective_damage_type();
946  resistances[resistance].insert(enemy.type_name());
947  }
948  }
949 
950  for (const auto& resist : resistances) {
951  int damage_with_resistance = round_damage(specials_damage, damage_multiplier * resist.first, damage_divisor);
952  tooltip << markup::bold(damage_with_resistance) << " "
953  << span_color(attack_info_percent_color(resist.first-100),
954  markup::italic("(", utils::signed_percent(resist.first-100), ")"))
955  << " : \t" // spaces to align the tab to a multiple of 8
956  << utils::join(resist.second, " " + font::unicode_bullet + " ") << '\n';
957  }
958  const string_with_tooltip damage_versus {flush(str), flush(tooltip)};
959 
960 #if 0
961  // We wanted to use the attack icon here, but couldn't find a good layout.
962  // The default images are 60x60 and have a 2-pixel transparent border. Trim it.
963  // The first SCALE() accounts for theoretically possible add-ons attack images larger than 60x60.
964  const std::string attack_icon = at.icon() + "~SCALE_INTO_SHARP(60,60)~CROP(2,2,56,56)~SCALE_INTO_SHARP(32,32)";
965  add_image(res, attack_icon, at.name());
966  add_text(res, " ", "");
967 #endif
968 
969  // The icons are 16x16. We add 5px padding for alignment reasons (placement of the icon in relation to ascender and descender letters).
970  const std::string spacer = "misc/blank.png~CROP(0, 0, 16, 21)"; // 21 == 16+5
971  add_image(res, spacer + "~BLIT(" + range_png + ",0,5)", damage_versus.tooltip);
972  add_image(res, spacer + "~BLIT(" + type_png + ",0,5)", damage_versus.tooltip);
973  for(auto sec_exist : secondary_types_png){
974  if(image::exists(sec_exist)){
975  add_image(res, spacer + "~BLIT(" + sec_exist + ",0,5)", damage_versus.tooltip);
976  }
977  }
978  add_text(res, damage_and_num_attacks.str, damage_and_num_attacks.tooltip);
979  add_text(res, damage_versus.str, damage_versus.tooltip); // This string is usually empty
980 
981  if(attack_alignment != u.alignment()){
982  const std::string align = unit_type::alignment_description(attack_alignment, u.gender());
983  const std::string align_id = unit_alignments::get_string(attack_alignment);
984 
985  color_t color = font::weapon_color;
986  if(tod_bonus != 0) {
987  color = (tod_bonus > 0) ? font::good_dmg_color : font::bad_dmg_color;
988  }
989 
990  str << " " << align << " (" << span_color(color, utils::signed_percent(tod_bonus)) << ")" << "\n";
991 
992  tooltip << _("Alignment:") << " " << markup::bold(align) << "\n"
993  << string_table[align_id + "_description" ] + "\n";
994 
995  add_text(res, flush(str), flush(tooltip));
996  }
997 
998  if(at.attacks_used() > 1)
999  {
1000  const std::string& attacks_used = VNGETTEXT(
1001  "uses $num attack",
1002  "uses $num attacks",
1003  at.attacks_used(),
1004  { {"num", std::to_string(at.attacks_used())} }
1005  );
1006  str << span_color(font::weapon_details_color, " ", attacks_used) << "\n";
1007 
1008  const std::string& attacks_used_tooltip = VNGETTEXT(
1009  "This attack uses $num attack point",
1010  "This attack uses $num attack points",
1011  at.attacks_used(),
1012  { {"num", std::to_string(at.attacks_used())} }
1013  );
1014  add_text(res, flush(str), attacks_used_tooltip);
1015  }
1016 
1017  const std::string &accuracy_parry = at.accuracy_parry_description();
1018  if (!accuracy_parry.empty())
1019  {
1020  str << span_color(font::weapon_details_color, " ", accuracy_parry) << "\n";
1021  add_text(res, flush(str), at.accuracy_parry_tooltip());
1022  }
1023  }
1024 
1025  {
1026  //If we have a second unit, do the 2-unit specials_context
1027  auto ctx = specials_context_t::make(
1028  { u.shared_from_this(), hex, at.shared_from_this() },
1029  { sec_u ? sec_u->shared_from_this() : nullptr, sec_u ? sec_u->get_location() : map_location(), sec_u_weapon },
1030  attacking);
1031 
1032  ctx.set_for_listing(true);
1033 
1034  boost::dynamic_bitset<> active;
1035  auto specials = ctx.special_tooltips(at, active);
1036  const std::size_t specials_size = specials.size();
1037  for ( std::size_t i = 0; i != specials_size; ++i )
1038  {
1039  // Aliases for readability:
1040  const auto& name = specials[i].name;
1041  const auto& description = specials[i].description;
1042  const color_t &details_color =
1044 
1045  str << span_color(details_color, " ", " ", name) << '\n';
1046  std::string help_page = "weaponspecial_" + specials[i].help_topic_id;
1047  tooltip << _("Weapon special:") << " " << markup::bold(name);
1048  if (!active[i]) {
1049  tooltip << markup::italic(_(" (inactive)"));
1050  }
1051  tooltip << '\n' << description;
1052 
1053  add_text(res, flush(str), flush(tooltip), help_page);
1054  }
1055 
1056  if(!specials.empty()) {
1057  // Add some padding so the end of the specials list
1058  // isn't too close vertically to the attack icons of
1059  // the next attack. Also for symmetry with the padding
1060  // above the list of specials (below the attack icon line).
1061  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1062  add_image(res, spacer, "");
1063  add_text(res, "\n", "");
1064  }
1065  }
1066 
1067  // 'abilities' version of special_tooltips is below.
1068  {
1069  //If we have a second unit, do the 2-unit specials_context
1070  auto ctx = specials_context_t::make(
1071  { u.shared_from_this(), hex, at.shared_from_this() },
1072  { sec_u ? sec_u->shared_from_this() : nullptr, sec_u ? sec_u->get_location() : map_location(), sec_u_weapon },
1073  attacking);
1074 
1075  boost::dynamic_bitset<> active;
1076  auto specials = ctx.abilities_special_tooltips(at, active);
1077  const std::size_t specials_size = specials.size();
1078  for ( std::size_t i = 0; i != specials_size; ++i )
1079  {
1080  // Aliases for readability:
1081  const auto& [name, description, help_topic_id] = specials[i];
1082  const color_t& details_color =
1084 
1085  str << span_color(details_color, " ", " ", name) << '\n';
1086  const std::string help_page = "weaponspecial_" + help_topic_id;
1087  tooltip << _("Weapon special:") << " " << markup::bold(name);
1088  if (!active[i]) {
1089  tooltip << markup::italic(_(" (inactive)"));
1090  }
1091  tooltip << '\n' << description;
1092 
1093  add_text(res, flush(str), flush(tooltip), help_page);
1094  }
1095 
1096  if(!specials.empty()) {
1097  // Add some padding so the end of the specials list
1098  // isn't too close vertically to the attack icons of
1099  // the next attack. Also for symmetry with the padding
1100  // above the list of specials (below the attack icon line).
1101  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1102  add_image(res, spacer, "");
1103  add_text(res, "\n", "");
1104  }
1105  }
1106  return damage;
1107 }
1108 
1109 // Conversion routine for both unscathed and damage change percentage.
1110 static std::string format_prob(double prob)
1111 {
1112  if(prob > 0.9995) {
1113  return "100%";
1114  } else if(prob < 0.0005) {
1115  return "0%";
1116  }
1117  std::ostringstream res;
1118  res << std::setprecision(prob < 0.01 ? 1 : prob < 0.1 ? 2 : 3) << 100.0 * prob << "%";
1119  return res.str();
1120 }
1121 
1122 static std::string format_hp(unsigned hp)
1123 {
1124  std::ostringstream res;
1125  res << ' ' << std::setw(3) << hp;
1126  return res.str();
1127 }
1128 
1129 static config unit_weapons(const reports::context& rc, const unit_const_ptr& attacker, const map_location &attacker_pos, const unit *defender, bool show_attacker)
1130 {
1131  if (!attacker || !defender) return config();
1132 
1133  const unit* u = show_attacker ? attacker.get() : defender;
1134  const unit* sec_u = !show_attacker ? attacker.get() : defender;
1135  const map_location unit_loc = show_attacker ? attacker_pos : defender->get_location();
1136 
1137  std::ostringstream str, tooltip;
1138  config res;
1139 
1140  std::vector<battle_context> weapons;
1141  for (unsigned i = 0; i < attacker->attacks().size(); i++) {
1142  // skip weapons with attack_weight=0
1143  if (attacker->attacks()[i].attack_weight() > 0) {
1144  weapons.emplace_back(rc.units(), attacker_pos, defender->get_location(), i, -1, 0.0, nullptr, attacker);
1145  }
1146  }
1147 
1148  for (const battle_context& weapon : weapons) {
1149 
1150  // Predict the battle outcome.
1151  combatant attacker_combatant(weapon.get_attacker_stats());
1152  combatant defender_combatant(weapon.get_defender_stats());
1153  attacker_combatant.fight(defender_combatant);
1154 
1155  const battle_context_unit_stats& context_unit_stats =
1156  show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1157  const battle_context_unit_stats& other_context_unit_stats =
1158  !show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1159 
1160  int total_damage = 0;
1161  int base_damage = 0;
1162  int num_blows = 0;
1163  int chance_to_hit = 0;
1164  t_string weapon_name = _("weapon^None");
1165 
1166  color_t dmg_color = font::weapon_color;
1167  if (context_unit_stats.weapon) {
1168  base_damage = attack_info(rc, *context_unit_stats.weapon, res, *u, unit_loc, sec_u, other_context_unit_stats.weapon);
1169  total_damage = context_unit_stats.damage;
1170  num_blows = context_unit_stats.num_blows;
1171  chance_to_hit = context_unit_stats.chance_to_hit;
1172  weapon_name = context_unit_stats.weapon->name();
1173 
1174  if ( total_damage > base_damage ) {
1175  dmg_color = font::good_dmg_color;
1176  } else if ( total_damage < base_damage ) {
1177  dmg_color = font::bad_dmg_color;
1178  }
1179  } else {
1180  str << span_color(font::weapon_color, weapon_name) << "\n";
1181  tooltip << _("Weapon:") << " " << markup::bold(weapon_name) << "\n"
1182  << _("Damage:") << " " << markup::bold("0") << "\n";
1183  }
1184 
1185  color_t chance_color = game_config::red_to_green(chance_to_hit);
1186 
1187  // Total damage.
1188  str << " " << span_color(dmg_color, total_damage)
1190  font::unicode_en_dash, num_blows, " (", span_color(chance_color, chance_to_hit, "%"), ")")
1191  << "\n";
1192 
1193  tooltip << _("Weapon:") << " " << markup::bold(weapon_name) << "\n"
1194  << _("Total damage") << markup::bold(total_damage) << "\n";
1195 
1196  // Create the hitpoints distribution.
1197  std::vector<std::pair<int, double>> hp_prob_vector;
1198 
1199  // First, we sort the probabilities in ascending order.
1200  std::vector<std::pair<double, int>> prob_hp_vector;
1201 
1202  combatant* c = show_attacker ? &attacker_combatant : &defender_combatant;
1203 
1204  int i = 0;
1205  for (double prob : c->hp_dist) {
1206  // We keep only values above 0.1%.
1207  if(prob > 0.001) {
1208  prob_hp_vector.emplace_back(prob, i);
1209  }
1210  i++;
1211  }
1212 
1213  std::sort(prob_hp_vector.begin(), prob_hp_vector.end());
1214 
1215  //TODO fendrin -- make that dynamically
1216  std::size_t max_hp_distrib_rows_ = 10;
1217 
1218  // We store a few of the highest probability hitpoint values.
1219  std::size_t nb_elem = std::min<std::size_t>(max_hp_distrib_rows_, prob_hp_vector.size());
1220 
1221  for(std::size_t i = prob_hp_vector.size() - nb_elem; i <prob_hp_vector.size(); i++) {
1222  hp_prob_vector.emplace_back(prob_hp_vector[i].second, prob_hp_vector[i].first);
1223  }
1224 
1225  // Then, we sort the hitpoint values in ascending order.
1226  std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
1227  // And reverse the order. Might be doable in a better manor.
1228  std::reverse(hp_prob_vector.begin(), hp_prob_vector.end());
1229 
1230  for (const auto& [hp, prob] : hp_prob_vector) {
1231  color_t prob_color = game_config::blue_to_white(prob * 100.0, true);
1232 
1234  " ", " ", span_color(u->hp_color(hp), format_hp(hp)),
1235  " ", font::weapon_numbers_sep, " ", span_color(prob_color, format_prob(prob)))
1236  << "\n";
1237  }
1238 
1239  add_text(res, flush(str), flush(tooltip));
1240  }
1241  return res;
1242 }
1243 
1244 /*
1245  * Display the attacks of the displayed unit against the unit passed as argument.
1246  * 'hex' is the location the attacker will be at during combat.
1247  */
1248 static config unit_weapons(const reports::context& rc, const unit* u, const map_location& hex)
1249 {
1250  config res;
1251  if ((u != nullptr) && (!u->attacks().empty())) {
1252  const std::string attack_headline = _n("Attack", "Attacks", u->attacks().size());
1253 
1254  add_text(res, span_color(font::weapon_details_color, attack_headline) + "\n", "");
1255 
1256  const auto left = u->attacks_left(false), max = u->max_attacks();
1257  if(max != 1) {
1258  // TRANSLATORS: This string is shown in the sidebar beneath the word "Attacks" when a unit can attack multiple times per turn
1259  const std::string line = VGETTEXT("Remaining: $left/$max",
1260  {{"left", std::to_string(left)},
1261  {"max", std::to_string(max)}});
1262  add_text(res, " " + span_color(font::weapon_details_color, line) + "\n",
1263  _("This unit can attack multiple times per turn."));
1264  }
1265 
1266  for (const attack_type& at : u->attacks())
1267  {
1268  attack_info(rc, at, res, *u, hex);
1269  }
1270  }
1271  return res;
1272 }
1274 {
1275  const unit *u = get_visible_unit(rc);
1276  const map_location& mouseover_hex = rc.screen().mouseover_hex();
1277  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
1278  const map_location& hex = mouseover_hex.valid() ? mouseover_hex : displayed_unit_hex;
1279  if (!u) return config();
1280 
1281  return unit_weapons(rc, u, hex);
1282 }
1283 REPORT_GENERATOR(highlighted_unit_weapons, rc)
1284 {
1286  const unit *sec_u = get_visible_unit(rc);
1287 
1288  if (!u) return report_unit_weapons(rc);
1289  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1290 
1291  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1292  map_location attack_loc;
1293  if (rc.mhb())
1294  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1295 
1296  if (!attack_loc.valid())
1297  return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1298 
1299  //TODO: shouldn't this pass sec_u as secodn parameter ?
1300  return unit_weapons(rc, u, attack_loc, sec_u, false);
1301 }
1302 REPORT_GENERATOR(selected_unit_weapons, rc)
1303 {
1305  const unit *sec_u = get_visible_unit(rc);
1306 
1307  if (!u) return config();
1308  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, u.get(), u->get_location());
1309 
1310  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1311  map_location attack_loc;
1312  if (rc.mhb())
1313  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1314 
1315  if (!attack_loc.valid())
1316  return unit_weapons(rc, u.get(), u->get_location());
1317 
1318  return unit_weapons(rc, u, attack_loc, sec_u, true);
1319 }
1320 
1321 REPORT_GENERATOR(unit_image,rc)
1322 {
1323  const unit *u = get_visible_unit(rc);
1324  if (!u) return config();
1325  return image_report(u->absolute_image() + u->image_mods());
1326 }
1327 REPORT_GENERATOR(selected_unit_image, rc)
1328 {
1329  const unit *u = get_selected_unit(rc);
1330  if (!u) return config();
1331  return image_report(u->absolute_image() + u->image_mods());
1332 }
1333 
1334 REPORT_GENERATOR(selected_unit_profile, rc)
1335 {
1336  const unit *u = get_selected_unit(rc);
1337  if (!u) return config();
1338  return image_report(u->small_profile());
1339 }
1340 REPORT_GENERATOR(unit_profile, rc)
1341 {
1342  const unit *u = get_visible_unit(rc);
1343  if (!u) return config();
1344  return image_report(u->small_profile());
1345 }
1346 
1347 static config tod_stats_at(const reports::context& rc, const map_location& hex)
1348 {
1349  std::ostringstream tooltip;
1350  std::ostringstream text;
1351 
1352  const map_location& tod_schedule_hex = (hex.valid() && !display::get_singleton()->shrouded(hex)) ? hex : map_location::null_location();
1353  const std::vector<time_of_day>& schedule = rc.tod().times(tod_schedule_hex);
1354 
1355  tooltip << _("Time of day schedule:") << " \n";
1356  int current = rc.tod().get_current_time(tod_schedule_hex);
1357  int i = 0;
1358  for (const time_of_day& tod : schedule) {
1359  if (i == current) {
1360  tooltip << markup::tag("big", markup::bold(tod.name)) << "\n";
1361  } else {
1362  tooltip << tod.name << "\n";
1363  }
1364  i++;
1365  }
1366 
1367  int times = schedule.size();
1368  text << current + 1 << "/" << times;
1369 
1370  return text_report(text.str(), tooltip.str(), "..schedule");
1371 }
1372 REPORT_GENERATOR(tod_stats, rc)
1373 {
1374  map_location mouseover_hex = rc.screen().mouseover_hex();
1375  if (mouseover_hex.valid()) return tod_stats_at(rc, mouseover_hex);
1376  return tod_stats_at(rc, rc.screen().selected_hex());
1377 }
1378 REPORT_GENERATOR(selected_tod_stats, rc)
1379 {
1380  const unit *u = get_selected_unit(rc);
1381  if(!u) return tod_stats_at(rc, map_location::null_location());
1382  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1383  const map_location& hex =
1384  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1385  return tod_stats_at(rc, hex);
1386 }
1387 
1388 static config time_of_day_at(const reports::context& rc, const map_location& mouseover_hex)
1389 {
1390  std::ostringstream tooltip;
1391  time_of_day tod = get_visible_time_of_day_at(rc, mouseover_hex);
1392 
1393  int b = tod.lawful_bonus;
1394  int l = generic_combat_modifier(b, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1395  std::string lawful_color("white");
1396  std::string chaotic_color("white");
1397  std::string liminal_color("white");
1398 
1399  if (b != 0) {
1400  lawful_color = (b > 0) ? "#0f0" : "#f00";
1401  chaotic_color = (b < 0) ? "#0f0" : "#f00";
1402  }
1403  if (l != 0) {
1404  liminal_color = (l > 0) ? "#0f0" : "#f00";
1405  }
1406  tooltip << _("Time of day:") << " " << markup::bold(tod.name) << "\n"
1407  << _("Lawful units:") << " "
1408  << markup::span_color(lawful_color, utils::signed_percent(b)) << "\n"
1409  << _("Neutral units:") << " " << utils::signed_percent(0) << '\n'
1410  << _("Chaotic units:") << " "
1411  << markup::span_color(chaotic_color, utils::signed_percent(-b)) << "\n"
1412  << _("Liminal units:") << " "
1413  << markup::span_color(liminal_color, utils::signed_percent(l)) << "\n";
1414 
1415  std::string tod_image = tod.image;
1416  if(tod.bonus_modified > 0) {
1417  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_bright << ")").str();
1418  } else if(tod.bonus_modified < 0) {
1419  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_dark << ")").str();
1420  }
1421 
1422  return image_report(tod_image, tooltip.str(), "time_of_day_" + tod.id);
1423 }
1425 {
1426  map_location mouseover_hex = rc.screen().mouseover_hex();
1427  if (mouseover_hex.valid()) return time_of_day_at(rc, mouseover_hex);
1428  return time_of_day_at(rc, rc.screen().selected_hex());
1429 }
1430 REPORT_GENERATOR(selected_time_of_day, rc)
1431 {
1432  const unit *u = get_selected_unit(rc);
1433  if(!u) return time_of_day_at(rc, map_location::null_location());
1434  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1435  const map_location& hex =
1436  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1437  return time_of_day_at(rc, hex);
1438 }
1439 
1440 static config unit_box_at(const reports::context& rc, const map_location& mouseover_hex)
1441 {
1442  std::ostringstream tooltip;
1443  time_of_day global_tod = rc.tod().get_time_of_day();
1444  time_of_day local_tod = get_visible_time_of_day_at(rc, mouseover_hex);
1445 
1446  int bonus = local_tod.lawful_bonus;
1447  int bonus_lim = generic_combat_modifier(bonus, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1448 
1449  std::string lawful_color("white");
1450  std::string chaotic_color("white");
1451  std::string liminal_color("white");
1452 
1453  if (bonus != 0) {
1454  lawful_color = (bonus > 0) ? "green" : "red";
1455  chaotic_color = (bonus < 0) ? "green" : "red";
1456  }
1457  if (bonus_lim != 0) {
1458  liminal_color = (bonus_lim > 0) ? "green" : "red";
1459  }
1460  tooltip << local_tod.name << '\n'
1461  << _("Lawful units:") << " "
1462  << markup::span_color(lawful_color, utils::signed_percent(bonus)) << "\n"
1463  << _("Neutral units:") << " " << utils::signed_percent(0) << '\n'
1464  << _("Chaotic units:") << " "
1465  << markup::span_color(chaotic_color, utils::signed_percent(-bonus)) << "\n"
1466  << _("Liminal units:") << " "
1467  << markup::span_color(liminal_color, utils::signed_percent(bonus_lim)) << "\n";
1468 
1469  std::string local_tod_image = "themes/classic/" + local_tod.image;
1470  std::string global_tod_image = "themes/classic/" + global_tod.image;
1471  if(local_tod.bonus_modified != 0) {
1472  local_tod_image += "~BLIT(";
1473  if (local_tod.bonus_modified > 0) {
1474  local_tod_image += game_config::images::tod_bright;
1475  } else if (local_tod.bonus_modified < 0) {
1476  local_tod_image += game_config::images::tod_dark;
1477  }
1478  local_tod_image += ")";
1479  }
1480 
1481  const gamemap &map = rc.map();
1482 
1483  //if (t_translation::terrain_matches(terrain, t_translation::ALL_OFF_MAP))
1484  // return config();
1485 
1486  //if (map.is_keep(mouseover_hex)) {
1487  // add_image(cfg, "icons/terrain/terrain_type_keep.png", "");
1488  //}
1489 
1490  std::string bg_terrain_image;
1491 
1492  for(const t_translation::terrain_code& underlying_terrain : map.get_terrain_info(mouseover_hex).union_type()) {
1493  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1494  bg_terrain_image = "~BLIT(unit_env/terrain/terrain-" + terrain_id + ".png)" + bg_terrain_image;
1495  }
1496 
1497  std::stringstream color;
1498  color << local_tod.color;
1499 
1500  bg_terrain_image = bg_terrain_image + "~CS(" + color.str() + ")";
1501 
1502  const unit* u = get_visible_unit(rc);
1503  std::string unit_image;
1504  if (u) {
1505  unit_image = "~BLIT(" + u->absolute_image() + u->image_mods() + ",35,22)";
1506  }
1507 
1508  std::string tod_image = global_tod_image + "~BLIT(" + local_tod_image + ")";
1509 
1510  return image_report(tod_image + bg_terrain_image + unit_image, tooltip.str(), "time_of_day");
1511 }
1512 REPORT_GENERATOR(unit_box, rc)
1513 {
1514  map_location mouseover_hex = rc.screen().mouseover_hex();
1515  return unit_box_at(rc, mouseover_hex);
1516 }
1517 
1518 
1520 {
1521  std::ostringstream str, tooltip;
1522  str << rc.tod().turn();
1523  int nb = rc.tod().number_of_turns();
1524  tooltip << _("Turn Number");
1525 
1526  if(nb != -1) {
1527  str << '/' << nb;
1528  tooltip << "\n\n" << _("When the game exceeds the number of turns indicated by the second number, it will end.");
1529  }
1530  return text_report(str.str(), tooltip.str());
1531 }
1532 
1534 {
1535  std::ostringstream str;
1536  const team& viewing_team = rc.screen().viewing_team();
1537  // Suppose the full unit map is applied.
1538  int fake_gold = viewing_team.gold();
1539  if (rc.wb()) {
1540  fake_gold -= rc.wb()->get_spent_gold_for(viewing_team.side());
1541  }
1542 
1543  if (!rc.screen().viewing_team_is_playing()) {
1545  } else if (fake_gold < 0) {
1547  } else {
1548  str << utils::half_signed_value(fake_gold);
1549  }
1550 
1551  return text_report(str.str(), _("Gold") + "\n\n" + _("The amount of gold currently available to recruit and maintain your army."));
1552 }
1553 
1554 REPORT_GENERATOR(villages, rc)
1555 {
1556  std::ostringstream str;
1557  const team &viewing_team = rc.screen().viewing_team();
1558  str << viewing_team.villages().size() << '/';
1559  if (viewing_team.uses_shroud()) {
1560  int unshrouded_villages = 0;
1561  for (const map_location &loc : rc.map().villages()) {
1562  if (!viewing_team.shrouded(loc))
1563  ++unshrouded_villages;
1564  }
1565  str << unshrouded_villages;
1566  } else {
1567  str << rc.map().villages().size();
1568  }
1569  return gray_inactive(rc,str.str(), _("Villages") + "\n\n" + _("The fraction of known villages that your side has captured."));
1570 }
1571 
1572 REPORT_GENERATOR(num_units, rc)
1573 {
1574  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."));
1575 }
1576 
1577 REPORT_GENERATOR(upkeep, rc)
1578 {
1579  std::ostringstream str;
1580  const team& viewing_team = rc.screen().viewing_team();
1581  team_data td(rc.dc(), viewing_team);
1582  str << td.expenses << " (" << td.upkeep << ")";
1583  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."));
1584 }
1585 
1586 REPORT_GENERATOR(expenses, rc)
1587 {
1588  const team& viewing_team = rc.screen().viewing_team();
1589  team_data td(rc.dc(), viewing_team);
1590  return gray_inactive(rc,std::to_string(td.expenses));
1591 }
1592 
1593 REPORT_GENERATOR(income, rc)
1594 {
1595  std::ostringstream str;
1596  const team& viewing_team = rc.screen().viewing_team();
1597  team_data td(rc.dc(), viewing_team);
1598 
1599  if (!rc.screen().viewing_team_is_playing()) {
1600  if (td.net_income < 0) {
1601  td.net_income = - td.net_income;
1603  } else {
1605  }
1606  } else if (td.net_income < 0) {
1607  td.net_income = - td.net_income;
1609  } else {
1610  str << td.net_income;
1611  }
1612 
1613  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."));
1614 }
1615 
1616 namespace {
1617 void blit_tced_icon(config &cfg, const std::string &terrain_id, const std::string &icon_image, bool high_res,
1618  const std::string &terrain_name) {
1619  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
1620  const std::string terrain_image = "terrain/" + icon_image + (high_res ? "_30.png" : ".png");
1621  add_image(cfg, tc_base + "~RC(magenta>" + terrain_id + ")~BLIT(" + terrain_image + ")", terrain_name);
1622 }
1623 }
1624 
1625 REPORT_GENERATOR(terrain_info, rc)
1626 {
1627  const gamemap& map = rc.map();
1628  map_location mouseover_hex = rc.screen().mouseover_hex();
1629 
1630  if(!map.on_board(mouseover_hex)) {
1631  mouseover_hex = rc.screen().selected_hex();
1632  }
1633 
1634  if(!map.on_board(mouseover_hex)) {
1635  return config();
1636  }
1637 
1638  const terrain_type& terrain = map.get_terrain_info(mouseover_hex);
1640  return config();
1641  }
1642 
1643  config cfg;
1644 
1645  bool high_res = false;
1646 
1647  if(display::get_singleton()->shrouded(mouseover_hex)) {
1648  return cfg;
1649  }
1650  //TODO
1651 // if (display::get_singleton()->fogged(mouseover_hex)) {
1652 // blit_tced_icon(cfg, "fog", high_res);
1653 // }
1654 //
1655 // if (map.is_keep(mouseover_hex)) {
1656 // blit_tced_icon(cfg, "keep", high_res);
1657 // }
1658 
1659  for(const t_translation::terrain_code& underlying_terrain : terrain.union_type()) {
1661  continue;
1662  }
1663  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1664  const std::string& terrain_name = map.get_terrain_string(underlying_terrain);
1665  const std::string& terrain_icon = map.get_terrain_info(underlying_terrain).icon_image();
1666  if(terrain_icon.empty()) {
1667  continue;
1668  }
1669  blit_tced_icon(cfg, terrain_id, terrain_icon, high_res, terrain_name);
1670  }
1671 
1672  if(map.is_village(mouseover_hex)) {
1673  int owner = rc.dc().village_owner(mouseover_hex);
1674  // This report is used in both game and editor. get_team(viewing_side) would throw in the editor's
1675  // terrain-only mode, but if the village already has an owner then we're not in that mode.
1676  if(owner != 0) {
1677  const team& viewing_team = rc.screen().viewing_team();
1678 
1679  if(!viewing_team.fogged(mouseover_hex)) {
1680  const team& owner_team = rc.dc().get_team(owner);
1681 
1682  std::string flag_icon = owner_team.flag_icon();
1683  std::string old_rgb = game_config::flag_rgb;
1684  std::string new_rgb = team::get_side_color_id(owner_team.side());
1685  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1686  if(flag_icon.empty()) {
1688  }
1689  std::string tooltip = side_tooltip(owner_team);
1690  std::string side = std::to_string(owner_team.side());
1691 
1692  add_image(cfg, flag_icon + mods, tooltip);
1693  add_text(cfg, side, tooltip);
1694  }
1695  }
1696  }
1697 
1698  return cfg;
1699 }
1700 
1701 REPORT_GENERATOR(terrain, rc)
1702 {
1703  const gamemap &map = rc.map();
1704  const team& viewing_team = rc.screen().viewing_team();
1705  map_location mouseover_hex = rc.screen().mouseover_hex();
1706  if (!map.on_board(mouseover_hex) || viewing_team.shrouded(mouseover_hex))
1707  return config();
1708 
1709  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1711  return config();
1712 
1713  std::ostringstream str;
1714  if (map.is_village(mouseover_hex))
1715  {
1716  int owner = rc.dc().village_owner(mouseover_hex);
1717  if (owner == 0 || viewing_team.fogged(mouseover_hex)) {
1718  str << map.get_terrain_info(terrain).income_description();
1719  } else if (owner == viewing_team.side()) {
1720  str << map.get_terrain_info(terrain).income_description_own();
1721  } else if (viewing_team.is_enemy(owner)) {
1722  str << map.get_terrain_info(terrain).income_description_enemy();
1723  } else {
1724  str << map.get_terrain_info(terrain).income_description_ally();
1725  }
1726 
1727  const std::string& underlying_desc = map.get_underlying_terrain_string(mouseover_hex);
1728  if(!underlying_desc.empty()) {
1729  str << underlying_desc;
1730  }
1731  } else {
1732  str << map.get_terrain_string(terrain);
1733  }
1734 
1735  return text_report(str.str());
1736 }
1737 
1738 REPORT_GENERATOR(zoom_level, rc)
1739 {
1740  std::ostringstream text;
1741  std::ostringstream tooltip;
1742  std::ostringstream help;
1743 
1744  text << static_cast<int>(rc.screen().get_zoom_factor() * 100) << "%";
1745 
1746  return text_report(text.str(), tooltip.str(), help.str());
1747 }
1748 
1749 REPORT_GENERATOR(position, rc)
1750 {
1751  const gamemap &map = rc.map();
1752  map_location mouseover_hex = rc.screen().mouseover_hex(),
1753  displayed_unit_hex = rc.screen().displayed_unit_hex(),
1754  selected_hex = rc.screen().selected_hex();
1755 
1756  if (!map.on_board(mouseover_hex)) {
1757  if (!map.on_board(selected_hex))
1758  return config();
1759  else {
1760  mouseover_hex = selected_hex;
1761  }
1762  }
1763 
1764  t_translation::terrain_code terrain = map[mouseover_hex];
1766  return config();
1767 
1768  std::ostringstream str;
1769  str << mouseover_hex;
1770 
1771  const unit *u = get_visible_unit(rc);
1772  const team &viewing_team = rc.screen().viewing_team();
1773  if (!u ||
1774  (displayed_unit_hex != mouseover_hex
1775  && displayed_unit_hex != rc.screen().selected_hex())
1776  || viewing_team.shrouded(mouseover_hex))
1777  {
1778  return text_report(str.str());
1779  }
1780 
1781  int move_cost = u->movement_cost(terrain);
1782  int defense = 100 - u->defense_modifier(terrain, mouseover_hex);
1783  if (move_cost < movetype::UNREACHABLE) {
1784  str << " " << defense << "%," << move_cost;
1785  } else if (mouseover_hex == displayed_unit_hex) {
1786  str << " " << defense << "%,‒";
1787  } else {
1788  str << " ‒";
1789  }
1790  return text_report(str.str());
1791 }
1792 
1793 REPORT_GENERATOR(side_playing, rc)
1794 {
1795  const team &active_team = rc.screen().playing_team();
1796  std::string flag_icon = active_team.flag_icon();
1797  std::string old_rgb = game_config::flag_rgb;
1798  std::string new_rgb = team::get_side_color_id(rc.screen().playing_team().side());
1799  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1800  if (flag_icon.empty()) {
1802  }
1803  return image_report(flag_icon + mods, side_tooltip(active_team));
1804 }
1805 
1806 REPORT_GENERATOR(observers, rc)
1807 {
1808  const std::set<std::string> &observers = rc.screen().observers();
1809  if (observers.empty())
1810  return config();
1811 
1812  std::ostringstream str;
1813  str << _("Observers:") << '\n';
1814  for (const std::string &obs : observers) {
1815  str << obs << '\n';
1816  }
1817  return image_report(game_config::images::observer, str.str());
1818 }
1819 
1820 REPORT_GENERATOR(report_clock, /*rc*/)
1821 {
1822  config report;
1824 
1826  ? "%I:%M %p"
1827  : "%H:%M";
1828 
1829  const auto now = std::chrono::system_clock::now();
1830  add_text(report, chrono::format_local_timestamp(now, format), _("Clock"));
1831 
1832  return report;
1833 }
1834 
1835 
1836 REPORT_GENERATOR(battery, /*rc*/)
1837 {
1838  config report;
1839 
1841  add_text(report, (boost::format("%.0f %%") % desktop::battery_info::get_battery_percentage()).str(), _("Battery"));
1842 
1843  return report;
1844 }
1845 
1846 REPORT_GENERATOR(report_countdown, rc)
1847 {
1848  using namespace std::chrono_literals;
1849  const team& viewing_team = rc.screen().viewing_team();
1850  if (viewing_team.countdown_time() == 0ms) {
1851  return report_report_clock(rc);
1852  }
1853 
1854  std::ostringstream time_str, formatted_time_str;
1855 
1856  using std::chrono::duration_cast;
1857 #ifdef __cpp_lib_format
1858  auto sec = duration_cast<std::chrono::seconds>(viewing_team.countdown_time());
1859  time_str << std::format("{:%M:%S}", sec);
1860 #else
1861  auto fmt = [](const auto& duration) -> std::string {
1862  return formatter{} << std::setw(2) << std::setfill('0') << duration.count();
1863  };
1864 
1865  // Create the time string
1866  auto sec = duration_cast<std::chrono::seconds>(viewing_team.countdown_time());
1867  auto min = duration_cast<std::chrono::minutes>(sec);
1868  time_str << fmt(min) << ':' << fmt(sec - min);
1869 #endif
1870 
1871  // Colorize the time string
1872  if (!rc.screen().viewing_team_is_playing()) {
1873  formatted_time_str << span_color(font::GRAY_COLOR, time_str.str());
1874  } else if (sec < 60s) {
1875  formatted_time_str << span_color("#c80000", time_str.str());
1876  } else if (sec < 120s) {
1877  formatted_time_str << span_color("#c8c800", time_str.str());
1878  } else {
1879  formatted_time_str << time_str.str();
1880  }
1881 
1882  config report;
1884  add_text(report, formatted_time_str.str(),
1885  _("Turn Countdown") + "\n\n" + _("Countdown until your turn automatically ends."));
1886 
1887  return report;
1888 }
1889 
1890 void reports::register_generator(const std::string &name, reports::generator *g)
1891 {
1892  dynamic_generators_[name].reset(g);
1893  all_reports_.clear();
1894 }
1895 
1896 config reports::generate_report(const std::string& name, const reports::context& rc)
1897 {
1898  const auto iter = dynamic_generators_.find(name);
1899  if(iter == dynamic_generators_.end()) {
1900  return generate_builtin_report(name, rc);
1901  }
1902 
1903  return iter->second->generate(rc);
1904 }
1905 
1906 config reports::generate_builtin_report(const std::string& name, const reports::context& rc)
1907 {
1908  const auto iter = static_generators.find(name);
1909  if(iter == static_generators.end()) {
1910  return config();
1911  }
1912 
1913  return std::invoke(iter->second, rc);
1914 }
1915 
1916 const std::set<std::string> &reports::report_list()
1917 {
1918  if (!all_reports_.empty()) return all_reports_;
1919  for (const static_report_generators::value_type &v : static_generators) {
1920  all_reports_.insert(v.first);
1921  }
1922  for (const dynamic_report_generators::value_type &v : dynamic_generators_) {
1923  all_reports_.insert(v.first);
1924  }
1925  return all_reports_;
1926 }
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:1890
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:1906
const std::set< std::string > & report_list()
Definition: reports.cpp:1916
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:1896
dynamic_report_generators dynamic_generators_
Definition: reports.hpp:96
void set_for_listing(bool for_listing)
Definition: abilities.hpp:282
std::vector< unit_ability_t::tooltip_info > abilities_special_tooltips(const attack_type &at, boost::dynamic_bitset<> &active_list) const
Definition: abilities.cpp:1372
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
Definition: abilities.hpp:271
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:735
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
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:478
static config unit_type(const unit *u)
Definition: reports.cpp:191
static std::string side_tooltip(const team &team)
Definition: reports.cpp:237
static config unit_jamming(const unit *u)
Definition: reports.cpp:661
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:1347
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:787
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:247
static config unit_defense(const reports::context &rc, const unit *u, const map_location &displayed_unit_hex)
Definition: reports.cpp:572
static std::string format_prob(double prob)
Definition: reports.cpp:1110
static config unit_status(const reports::context &rc, const unit *u)
Definition: reports.cpp:337
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:279
static config unit_vision(const unit *u)
Definition: reports.cpp:638
static const color_t attack_info_percent_color(int resistance)
Maps resistance <= -60 (resistance value <= -60%) to intense red.
Definition: reports.cpp:781
static config unit_traits(const unit *u)
Definition: reports.cpp:307
static config unit_abilities_report(const unit *u, const map_location &loc)
Definition: reports.cpp:416
static config unit_box_at(const reports::context &rc, const map_location &mouseover_hex)
Definition: reports.cpp:1440
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:218
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:1388
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:1129
#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:679
static config unit_alignment(const reports::context &rc, const unit *u, const map_location &hex)
Definition: reports.cpp:379
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:1122
static config unit_advancement_options(const unit *u)
Definition: reports.cpp:552
static config unit_xp(const unit *u)
Definition: reports.cpp:527
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