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