The Battle for Wesnoth  1.19.13+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 
416 static config unit_abilities(const unit* u, const map_location& loc)
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& [id, base_name, display_name, description] = 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_" + id + base_name.base_str());
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(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(u, mouseover_hex);
473  else
474  return unit_abilities(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 t_translation::terrain_code &terrain = map[displayed_unit_hex];
585  int def = 100 - u->defense_modifier(terrain);
586  color_t color = game_config::red_to_green(def);
587  str << span_color(color, def, '%');
588  tooltip << _("Terrain:") << " " << markup::bold(map.get_terrain_info(terrain).description()) << "\n";
589 
590  const t_translation::ter_list &underlyings = map.underlying_def_terrain(terrain);
591  if (underlyings.size() != 1 || underlyings.front() != terrain)
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  // TODO
643  std::ostringstream str, tooltip;
644  if (u->vision() != u->total_movement()) {
645  str << _("vision:") << ' ' << u->vision();
646  tooltip << _("vision:") << ' ' << u->vision() << '\n';
647  }
648  if (u->jamming() != 0) {
649  if (static_cast<std::streamoff>(str.tellp()) == 0)
650  str << _("jamming:") << ' ' << u->jamming();
651  tooltip << _("jamming:") << ' ' << u->jamming() << '\n';
652  }
653  return text_report(str.str(), tooltip.str());
654 }
656 {
657  const unit* u = get_visible_unit(rc);
658  return unit_vision(u);
659 }
660 REPORT_GENERATOR(selected_unit_vision, rc)
661 {
662  const unit* u = get_selected_unit(rc);
663  return unit_vision(u);
664 }
665 
666 static config unit_moves(const reports::context& rc, const unit* u, bool is_visible_unit)
667 {
668  if (!u) return config();
669  std::ostringstream str, tooltip;
670  double movement_frac = 1.0;
671 
672  std::set<terrain_movement> terrain_moves;
673 
674  if (u->side() == rc.screen().playing_team().side()) {
675  movement_frac = static_cast<double>(u->movement_left()) / std::max<int>(1, u->total_movement());
676  if (movement_frac > 1.0)
677  movement_frac = 1.0;
678  }
679 
680  tooltip << _("Movement Costs:") << "\n";
681  for (t_translation::terrain_code terrain : prefs::get().encountered_terrains()) {
683  continue;
684 
685  const terrain_type& info = rc.map().get_terrain_info(terrain);
686 
687  if (info.union_type().size() == 1 && info.union_type()[0] == info.number() && info.is_nonnull()) {
688  terrain_moves.emplace(info.name(), u->movement_cost(terrain));
689  }
690  }
691 
692  for (const terrain_movement& tm : terrain_moves) {
693  tooltip << tm.name << ": ";
694 
695  //movement - range: 1 .. 5, movetype::UNREACHABLE=impassable
696  const bool cannot_move = tm.moves > u->total_movement(); // cannot move in this terrain
697  double movement_red_to_green = 100.0 - 25.0 * tm.moves;
698 
699  std::stringstream temp_str;
700  // A 5 MP margin; if the movement costs go above
701  // the unit's max moves + 5, we replace it with dashes.
702  if (cannot_move && (tm.moves > u->total_movement() + 5)) {
703  temp_str << font::unicode_figure_dash;
704  } else if (cannot_move) {
705  temp_str << "(" << tm.moves << ")";
706  } else {
707  temp_str << tm.moves;
708  }
709  if (tm.moves != 0) {
710  const int movement_hexes_per_turn = u->total_movement() / tm.moves;
711  temp_str << " ";
712  for (int i = 0; i < movement_hexes_per_turn; ++i) {
713  // Unicode horizontal black hexagon and Unicode zero width space (to allow a line break)
714  temp_str << "\u2b23\u200b";
715  }
716  }
717 
718  // passing true to select the less saturated red-to-green scale
719  color_t color = game_config::red_to_green(movement_red_to_green, true);
720  tooltip << span_color(color, temp_str.str()) << '\n';
721 
722  }
723 
724  int grey = 128 + static_cast<int>((255 - 128) * movement_frac);
725  color_t c = color_t(grey, grey, grey);
726  int numerator = u->movement_left();
727  if(is_visible_unit) {
729  if(route.steps.size() > 0) {
730  numerator -= route.move_cost;
731  if(numerator < 0) {
732  // Multi-turn move
733  // TODO: what to show in this case?
734  numerator = 0;
735  }
736 
737  // If the route captures a village, assume that uses up all remaining MP.
738  const auto& end = route.marks.find(route.steps.back());
739  if(end != route.marks.end() && end->second.capture) {
740  numerator = 0;
741  }
742  } else {
743  // TODO: if the mouseover hex is unreachable (for example, a deep water hex
744  // and the current unit is a land unit), what to show?
745  }
746  }
747  str << span_color(c, numerator, '/', u->total_movement());
748  return text_report(str.str(), tooltip.str());
749 }
751 {
752  const unit *u = get_visible_unit(rc);
753  return unit_moves(rc, u, true);
754 }
755 REPORT_GENERATOR(selected_unit_moves, rc)
756 {
757  const unit *u = get_selected_unit(rc);
758  return unit_moves(rc, u, false);
759 }
760 
761 /**
762  * Maps resistance <= -60 (resistance value <= -60%) to intense red.
763  * Maps resistance >= 60 (resistance value >= 60%) to intense green.
764  * Intermediate values are affinely mapped to the red-to-green scale,
765  * with 0 (0%) being mapped to yellow.
766  * Compare unit_helper::resistance_color().
767  */
768 static inline const color_t attack_info_percent_color(int resistance)
769 {
770  // Passing false to select the more saturated red-to-green scale.
771  return game_config::red_to_green(50.0 + resistance * 5.0 / 6.0, false);
772 }
773 
774 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)
775 {
776  std::ostringstream str, tooltip;
777  int damage = 0;
778 
779  struct string_with_tooltip {
780  std::string str;
781  std::string tooltip;
782  };
783 
784  {
785  auto ctx = at.specials_context(u.shared_from_this(), hex, u.side() == rc.screen().playing_team().side());
786  int base_damage = at.damage();
787  double specials_damage = at.modified_damage();
788  int damage_multiplier = 100;
789  const_attack_ptr weapon = at.shared_from_this();
790  unit_alignments::type attack_alignment = weapon->alignment().value_or(u.alignment());
791  int tod_bonus = combat_modifier(get_visible_time_of_day_at(rc, hex), attack_alignment, u.is_fearless());
792  damage_multiplier += tod_bonus;
793  int leader_bonus = under_leadership(u, hex, weapon);
794  if (leader_bonus != 0)
795  damage_multiplier += leader_bonus;
796 
798  int damage_divisor = slowed ? 20000 : 10000;
799  // Assume no specific resistance (i.e. multiply by 100).
800  damage = round_damage(specials_damage, damage_multiplier * 100, damage_divisor);
801 
802  // Hit points are used to calculate swarm, so they need to be bounded.
803  unsigned max_hp = u.max_hitpoints();
804  unsigned cur_hp = std::min<unsigned>(std::max(0, u.hitpoints()), max_hp);
805 
806  unsigned base_attacks = at.num_attacks();
807  unsigned min_attacks, max_attacks;
808  at.modified_attacks(min_attacks, max_attacks);
809  unsigned num_attacks = swarm_blows(min_attacks, max_attacks, cur_hp, max_hp);
810 
811  color_t dmg_color = font::weapon_color;
812  if (damage > std::round(specials_damage)) {
813  dmg_color = font::good_dmg_color;
814  } else if (damage < std::round(specials_damage)) {
815  dmg_color = font::bad_dmg_color;
816  }
817 
818  str << span_color(dmg_color, " ", damage)
819  << span_color(font::weapon_color, font::weapon_numbers_sep, num_attacks, ' ', at.name())
820  << "\n";
821  tooltip << _("Weapon:") << " " << markup::bold(at.name()) << "\n"
822  << _("Damage:") << " " << markup::bold(damage) << "\n";
823 
824  if ( tod_bonus || leader_bonus || slowed || specials_damage != base_damage )
825  {
826  tooltip << '\t' << _("Base damage:") << " " << base_damage << '\n';
827  if ( specials_damage != base_damage ) {
828  tooltip << '\t' << _("With specials:") << " " << specials_damage << '\n';
829  }
830  if (tod_bonus) {
831  tooltip << '\t' << _("Time of day:") << " "
832  << utils::signed_percent(tod_bonus) << '\n';
833  }
834  if (leader_bonus) {
835  tooltip << '\t' << _("Leadership:") << " "
836  << utils::signed_percent(leader_bonus) << '\n';
837  }
838  if (slowed) {
839  tooltip << '\t' << _("Slowed:") << " " << "/ 2" << '\n';
840  }
841  }
842 
843  tooltip << _("Attacks:") << " " << markup::bold(num_attacks) << "\n";
844  if ( max_attacks != min_attacks && cur_hp != max_hp ) {
845  if ( max_attacks < min_attacks ) {
846  // "Reverse swarm"
847  tooltip << '\t' << _("Max swarm bonus:") << " " << (min_attacks-max_attacks) << '\n';
848  tooltip << '\t' << _("Swarm:") << " " << "* "<< (100 - cur_hp*100/max_hp) << "%\n";
849  tooltip << '\t' << _("Base attacks:") << " " << '+' << base_attacks << '\n';
850  // The specials line will not necessarily match up with how the
851  // specials are calculated, but for an unusual case, simple brevity
852  // trumps complexities.
853  if ( max_attacks != base_attacks ) {
854  int attack_diff = static_cast<int>(max_attacks) - static_cast<int>(base_attacks);
855  tooltip << '\t' << _("Specials:") << " " << utils::signed_value(attack_diff) << '\n';
856  }
857  }
858  else {
859  // Regular swarm
860  tooltip << '\t' << _("Base attacks:") << " " << base_attacks << '\n';
861  if ( max_attacks != base_attacks ) {
862  tooltip << '\t' << _("With specials:") << " " << max_attacks << '\n';
863  }
864  if ( min_attacks != 0 ) {
865  tooltip << '\t' << _("Subject to swarm:") << " " << (max_attacks-min_attacks) << '\n';
866  }
867  tooltip << '\t' << _("Swarm:") << " " << "* "<< (cur_hp*100/max_hp) << "%\n";
868  }
869  }
870  else if ( num_attacks != base_attacks ) {
871  tooltip << '\t' << _("Base attacks:") << " " << base_attacks << '\n';
872  tooltip << '\t' << _("With specials:") << " " << num_attacks << '\n';
873  }
874 
875  const string_with_tooltip damage_and_num_attacks {flush(str), flush(tooltip)};
876 
877  std::string range = string_table["range_" + at.range()];
878  std::pair<std::string, std::set<std::string>> all_damage_types = at.damage_types();
879  std::string type = all_damage_types.first;
880  std::set<std::string> alt_types = all_damage_types.second;
881  std::string lang_type = string_table["type_" + type];
882  for(auto alt_t : alt_types){
883  lang_type += ", " + string_table["type_" + alt_t];
884  }
885 
886  // SCALE_INTO() is needed in case the 72x72 images/misc/missing-image.png is substituted.
887  const std::string range_png = std::string("icons/profiles/") + at.range() + "_attack.png~SCALE_INTO(16,16)";
888  const std::string type_png = std::string("icons/profiles/") + type + ".png~SCALE_INTO(16,16)";
889  std::vector<std::string> secondary_types_png;
890  for(const auto& alt_t : alt_types) {
891  secondary_types_png.push_back(std::string("icons/profiles/") + alt_t + ".png~SCALE_INTO(16,16)");
892  }
893 
894  // If any of the images is missing, then add a text description too.
895  bool all_pngs_exist = image::exists(range_png);
896  all_pngs_exist &= image::exists(type_png);
897  for(const auto& png : secondary_types_png) {
898  all_pngs_exist &= image::exists(png);
899  }
900  if(!all_pngs_exist) {
901  str << span_color(font::weapon_details_color, " ", " ", range, font::weapon_details_sep, lang_type)
902  << "\n";
903  }
904 
905  tooltip << _("Weapon range:") << " " << markup::bold(range) << "\n"
906  << _("Damage type:") << " " << markup::bold(lang_type) << "\n"
907  << _("Damage versus:") << " " << '\n';
908 
909  // Show this weapon damage and resistance against all the different units.
910  // We want weak resistances (= good damage) first.
911  std::map<int, std::set<std::string>, std::greater<int>> resistances;
912  std::set<std::string> seen_types;
913  const team &unit_team = rc.dc().get_team(u.side());
914  const team &viewing_team = rc.screen().viewing_team();
915  for (const unit &enemy : rc.units())
916  {
917  if (enemy.incapacitated()) //we can't attack statues so don't display them in this tooltip
918  continue;
919  if (!unit_team.is_enemy(enemy.side()))
920  continue;
921  const map_location &loc = enemy.get_location();
922  const bool see_all = game_config::debug || rc.screen().show_everything();
923  if (!enemy.is_visible_to_team(viewing_team, see_all))
924  continue;
925  bool new_type = seen_types.insert(enemy.type_id()).second;
926  if (new_type) {
927  auto ctx = at.specials_context(u.shared_from_this(), enemy.shared_from_this(), hex, loc, u.side() == rc.screen().playing_team().side(), nullptr);
928  int resistance = enemy.resistance_against(at, false, loc, nullptr);
929  resistances[resistance].insert(enemy.type_name());
930  }
931  }
932 
933  for (const auto& resist : resistances) {
934  int damage_with_resistance = round_damage(specials_damage, damage_multiplier * resist.first, damage_divisor);
935  tooltip << markup::bold(damage_with_resistance) << " "
936  << span_color(attack_info_percent_color(resist.first-100),
937  markup::italic("(", utils::signed_percent(resist.first-100), ")"))
938  << " : \t" // spaces to align the tab to a multiple of 8
939  << utils::join(resist.second, " " + font::unicode_bullet + " ") << '\n';
940  }
941  const string_with_tooltip damage_versus {flush(str), flush(tooltip)};
942 
943 #if 0
944  // We wanted to use the attack icon here, but couldn't find a good layout.
945  // The default images are 60x60 and have a 2-pixel transparent border. Trim it.
946  // The first SCALE() accounts for theoretically possible add-ons attack images larger than 60x60.
947  const std::string attack_icon = at.icon() + "~SCALE_INTO_SHARP(60,60)~CROP(2,2,56,56)~SCALE_INTO_SHARP(32,32)";
948  add_image(res, attack_icon, at.name());
949  add_text(res, " ", "");
950 #endif
951 
952  // The icons are 16x16. We add 5px padding for alignment reasons (placement of the icon in relation to ascender and descender letters).
953  const std::string spacer = "misc/blank.png~CROP(0, 0, 16, 21)"; // 21 == 16+5
954  add_image(res, spacer + "~BLIT(" + range_png + ",0,5)", damage_versus.tooltip);
955  add_image(res, spacer + "~BLIT(" + type_png + ",0,5)", damage_versus.tooltip);
956  for(auto sec_exist : secondary_types_png){
957  if(image::exists(sec_exist)){
958  add_image(res, spacer + "~BLIT(" + sec_exist + ",0,5)", damage_versus.tooltip);
959  }
960  }
961  add_text(res, damage_and_num_attacks.str, damage_and_num_attacks.tooltip);
962  add_text(res, damage_versus.str, damage_versus.tooltip); // This string is usually empty
963 
964  if(attack_alignment != u.alignment()){
965  const std::string align = unit_type::alignment_description(attack_alignment, u.gender());
966  const std::string align_id = unit_alignments::get_string(attack_alignment);
967 
968  color_t color = font::weapon_color;
969  if(tod_bonus != 0) {
970  color = (tod_bonus > 0) ? font::good_dmg_color : font::bad_dmg_color;
971  }
972 
973  str << " " << align << " (" << span_color(color, utils::signed_percent(tod_bonus)) << ")" << "\n";
974 
975  tooltip << _("Alignment:") << " " << markup::bold(align) << "\n"
976  << string_table[align_id + "_description" ] + "\n";
977 
978  add_text(res, flush(str), flush(tooltip));
979  }
980 
981  const std::string &accuracy_parry = at.accuracy_parry_description();
982  if (!accuracy_parry.empty())
983  {
984  str << span_color(font::weapon_details_color, " ", accuracy_parry) << "\n";
985  add_text(res, flush(str), at.accuracy_parry_tooltip());
986  }
987  }
988 
989  {
990  //If we have a second unit, do the 2-unit specials_context
991  bool attacking = (u.side() == rc.screen().playing_team().side());
992  auto ctx = (sec_u == nullptr) ? at.specials_context_for_listing(attacking) :
993  at.specials_context(u.shared_from_this(), sec_u->shared_from_this(), hex, sec_u->get_location(), attacking, std::move(sec_u_weapon));
994 
995  boost::dynamic_bitset<> active;
996  const std::vector<std::pair<t_string, t_string>> &specials = at.special_tooltips(&active);
997  const std::size_t specials_size = specials.size();
998  for ( std::size_t i = 0; i != specials_size; ++i )
999  {
1000  // Aliases for readability:
1001  const t_string &name = specials[i].first;
1002  const t_string &description = specials[i].second;
1003  const color_t &details_color =
1005 
1006  str << span_color(details_color, " ", " ", name) << '\n';
1007  std::string help_page = "weaponspecial_" + name.base_str();
1008  tooltip << _("Weapon special:") << " " << markup::bold(name);
1009  if (!active[i]) {
1010  tooltip << markup::italic(_(" (inactive)"));
1011  }
1012  tooltip << '\n' << description;
1013 
1014  add_text(res, flush(str), flush(tooltip), help_page);
1015  }
1016 
1017  if(!specials.empty()) {
1018  // Add some padding so the end of the specials list
1019  // isn't too close vertically to the attack icons of
1020  // the next attack. Also for symmetry with the padding
1021  // above the list of specials (below the attack icon line).
1022  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1023  add_image(res, spacer, "");
1024  add_text(res, "\n", "");
1025  }
1026  }
1027 
1028  // 'abilities' version of special_tooltips is below.
1029  {
1030  //If we have a second unit, do the 2-unit specials_context
1031  bool attacking = (u.side() == rc.screen().playing_team().side());
1032  auto ctx = (sec_u == nullptr)
1033  ? at.specials_context(u.shared_from_this(), hex, attacking)
1034  : at.specials_context(u.shared_from_this(), sec_u->shared_from_this(), hex, sec_u->get_location(), attacking, std::move(sec_u_weapon));
1035 
1036  boost::dynamic_bitset<> active;
1037  const std::vector<std::pair<t_string, t_string>>& specials = at.abilities_special_tooltips(&active);
1038  const std::size_t specials_size = specials.size();
1039  for ( std::size_t i = 0; i != specials_size; ++i )
1040  {
1041  // Aliases for readability:
1042  const auto& [name, description] = specials[i];
1043  const color_t& details_color =
1045 
1046  str << span_color(details_color, " ", " ", name) << '\n';
1047  const std::string help_page = "weaponspecial_" + name.base_str();
1048  tooltip << _("Weapon special:") << " " << markup::bold(name);
1049  if (!active[i]) {
1050  tooltip << markup::italic(_(" (inactive)"));
1051  }
1052  tooltip << '\n' << description;
1053 
1054  add_text(res, flush(str), flush(tooltip), help_page);
1055  }
1056 
1057  if(!specials.empty()) {
1058  // Add some padding so the end of the specials list
1059  // isn't too close vertically to the attack icons of
1060  // the next attack. Also for symmetry with the padding
1061  // above the list of specials (below the attack icon line).
1062  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1063  add_image(res, spacer, "");
1064  add_text(res, "\n", "");
1065  }
1066  }
1067  return damage;
1068 }
1069 
1070 // Conversion routine for both unscathed and damage change percentage.
1071 static std::string format_prob(double prob)
1072 {
1073  if(prob > 0.9995) {
1074  return "100%";
1075  } else if(prob < 0.0005) {
1076  return "0%";
1077  }
1078  std::ostringstream res;
1079  res << std::setprecision(prob < 0.01 ? 1 : prob < 0.1 ? 2 : 3) << 100.0 * prob << "%";
1080  return res.str();
1081 }
1082 
1083 static std::string format_hp(unsigned hp)
1084 {
1085  std::ostringstream res;
1086  res << ' ' << std::setw(3) << hp;
1087  return res.str();
1088 }
1089 
1090 static config unit_weapons(const reports::context& rc, const unit_const_ptr& attacker, const map_location &attacker_pos, const unit *defender, bool show_attacker)
1091 {
1092  if (!attacker || !defender) return config();
1093 
1094  const unit* u = show_attacker ? attacker.get() : defender;
1095  const unit* sec_u = !show_attacker ? attacker.get() : defender;
1096  const map_location unit_loc = show_attacker ? attacker_pos : defender->get_location();
1097 
1098  std::ostringstream str, tooltip;
1099  config res;
1100 
1101  std::vector<battle_context> weapons;
1102  for (unsigned i = 0; i < attacker->attacks().size(); i++) {
1103  // skip weapons with attack_weight=0
1104  if (attacker->attacks()[i].attack_weight() > 0) {
1105  weapons.emplace_back(rc.units(), attacker_pos, defender->get_location(), i, -1, 0.0, nullptr, attacker);
1106  }
1107  }
1108 
1109  for (const battle_context& weapon : weapons) {
1110 
1111  // Predict the battle outcome.
1112  combatant attacker_combatant(weapon.get_attacker_stats());
1113  combatant defender_combatant(weapon.get_defender_stats());
1114  attacker_combatant.fight(defender_combatant);
1115 
1116  const battle_context_unit_stats& context_unit_stats =
1117  show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1118  const battle_context_unit_stats& other_context_unit_stats =
1119  !show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1120 
1121  int total_damage = 0;
1122  int base_damage = 0;
1123  int num_blows = 0;
1124  int chance_to_hit = 0;
1125  t_string weapon_name = _("weapon^None");
1126 
1127  color_t dmg_color = font::weapon_color;
1128  if (context_unit_stats.weapon) {
1129  base_damage = attack_info(rc, *context_unit_stats.weapon, res, *u, unit_loc, sec_u, other_context_unit_stats.weapon);
1130  total_damage = context_unit_stats.damage;
1131  num_blows = context_unit_stats.num_blows;
1132  chance_to_hit = context_unit_stats.chance_to_hit;
1133  weapon_name = context_unit_stats.weapon->name();
1134 
1135  if ( total_damage > base_damage ) {
1136  dmg_color = font::good_dmg_color;
1137  } else if ( total_damage < base_damage ) {
1138  dmg_color = font::bad_dmg_color;
1139  }
1140  } else {
1141  str << span_color(font::weapon_color, weapon_name) << "\n";
1142  tooltip << _("Weapon:") << " " << markup::bold(weapon_name) << "\n"
1143  << _("Damage:") << " " << markup::bold("0") << "\n";
1144  }
1145 
1146  color_t chance_color = game_config::red_to_green(chance_to_hit);
1147 
1148  // Total damage.
1149  str << " " << span_color(dmg_color, total_damage)
1151  font::unicode_en_dash, num_blows, " (", span_color(chance_color, chance_to_hit, "%"), ")")
1152  << "\n";
1153 
1154  tooltip << _("Weapon:") << " " << markup::bold(weapon_name) << "\n"
1155  << _("Total damage") << markup::bold(total_damage) << "\n";
1156 
1157  // Create the hitpoints distribution.
1158  std::vector<std::pair<int, double>> hp_prob_vector;
1159 
1160  // First, we sort the probabilities in ascending order.
1161  std::vector<std::pair<double, int>> prob_hp_vector;
1162 
1163  combatant* c = show_attacker ? &attacker_combatant : &defender_combatant;
1164 
1165  int i = 0;
1166  for (double prob : c->hp_dist) {
1167  // We keep only values above 0.1%.
1168  if(prob > 0.001) {
1169  prob_hp_vector.emplace_back(prob, i);
1170  }
1171  i++;
1172  }
1173 
1174  std::sort(prob_hp_vector.begin(), prob_hp_vector.end());
1175 
1176  //TODO fendrin -- make that dynamically
1177  std::size_t max_hp_distrib_rows_ = 10;
1178 
1179  // We store a few of the highest probability hitpoint values.
1180  std::size_t nb_elem = std::min<std::size_t>(max_hp_distrib_rows_, prob_hp_vector.size());
1181 
1182  for(std::size_t i = prob_hp_vector.size() - nb_elem; i <prob_hp_vector.size(); i++) {
1183  hp_prob_vector.emplace_back(prob_hp_vector[i].second, prob_hp_vector[i].first);
1184  }
1185 
1186  // Then, we sort the hitpoint values in ascending order.
1187  std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
1188  // And reverse the order. Might be doable in a better manor.
1189  std::reverse(hp_prob_vector.begin(), hp_prob_vector.end());
1190 
1191  for (const auto& [hp, prob] : hp_prob_vector) {
1192  color_t prob_color = game_config::blue_to_white(prob * 100.0, true);
1193 
1195  " ", " ", span_color(u->hp_color(hp), format_hp(hp)),
1196  " ", font::weapon_numbers_sep, " ", span_color(prob_color, format_prob(prob)))
1197  << "\n";
1198  }
1199 
1200  add_text(res, flush(str), flush(tooltip));
1201  }
1202  return res;
1203 }
1204 
1205 /*
1206  * Display the attacks of the displayed unit against the unit passed as argument.
1207  * 'hex' is the location the attacker will be at during combat.
1208  */
1209 static config unit_weapons(const reports::context& rc, const unit* u, const map_location& hex)
1210 {
1211  config res;
1212  if ((u != nullptr) && (!u->attacks().empty())) {
1213  const std::string attack_headline = _n("Attack", "Attacks", u->attacks().size());
1214 
1215  add_text(res, span_color(font::weapon_details_color, attack_headline) + "\n", "");
1216 
1217  const auto left = u->attacks_left(false), max = u->max_attacks();
1218  if(max != 1) {
1219  // TRANSLATORS: This string is shown in the sidebar beneath the word "Attacks" when a unit can attack multiple times per turn
1220  const std::string line = VGETTEXT("Remaining: $left/$max",
1221  {{"left", std::to_string(left)},
1222  {"max", std::to_string(max)}});
1223  add_text(res, " " + span_color(font::weapon_details_color, line) + "\n",
1224  _("This unit can attack multiple times per turn."));
1225  }
1226 
1227  for (const attack_type& at : u->attacks())
1228  {
1229  attack_info(rc, at, res, *u, hex);
1230  }
1231  }
1232  return res;
1233 }
1235 {
1236  const unit *u = get_visible_unit(rc);
1237  const map_location& mouseover_hex = rc.screen().mouseover_hex();
1238  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
1239  const map_location& hex = mouseover_hex.valid() ? mouseover_hex : displayed_unit_hex;
1240  if (!u) return config();
1241 
1242  return unit_weapons(rc, u, hex);
1243 }
1244 REPORT_GENERATOR(highlighted_unit_weapons, rc)
1245 {
1247  const unit *sec_u = get_visible_unit(rc);
1248 
1249  if (!u) return report_unit_weapons(rc);
1250  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1251 
1252  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1253  map_location attack_loc;
1254  if (rc.mhb())
1255  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1256 
1257  if (!attack_loc.valid())
1258  return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1259 
1260  //TODO: shouldn't this pass sec_u as secodn parameter ?
1261  return unit_weapons(rc, u, attack_loc, sec_u, false);
1262 }
1263 REPORT_GENERATOR(selected_unit_weapons, rc)
1264 {
1266  const unit *sec_u = get_visible_unit(rc);
1267 
1268  if (!u) return config();
1269  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, u.get(), u->get_location());
1270 
1271  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1272  map_location attack_loc;
1273  if (rc.mhb())
1274  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1275 
1276  if (!attack_loc.valid())
1277  return unit_weapons(rc, u.get(), u->get_location());
1278 
1279  return unit_weapons(rc, u, attack_loc, sec_u, true);
1280 }
1281 
1282 REPORT_GENERATOR(unit_image,rc)
1283 {
1284  const unit *u = get_visible_unit(rc);
1285  if (!u) return config();
1286  return image_report(u->absolute_image() + u->image_mods());
1287 }
1288 REPORT_GENERATOR(selected_unit_image, rc)
1289 {
1290  const unit *u = get_selected_unit(rc);
1291  if (!u) return config();
1292  return image_report(u->absolute_image() + u->image_mods());
1293 }
1294 
1295 REPORT_GENERATOR(selected_unit_profile, rc)
1296 {
1297  const unit *u = get_selected_unit(rc);
1298  if (!u) return config();
1299  return image_report(u->small_profile());
1300 }
1301 REPORT_GENERATOR(unit_profile, rc)
1302 {
1303  const unit *u = get_visible_unit(rc);
1304  if (!u) return config();
1305  return image_report(u->small_profile());
1306 }
1307 
1308 static config tod_stats_at(const reports::context& rc, const map_location& hex)
1309 {
1310  std::ostringstream tooltip;
1311  std::ostringstream text;
1312 
1313  const map_location& tod_schedule_hex = (hex.valid() && !display::get_singleton()->shrouded(hex)) ? hex : map_location::null_location();
1314  const std::vector<time_of_day>& schedule = rc.tod().times(tod_schedule_hex);
1315 
1316  tooltip << _("Time of day schedule:") << " \n";
1317  int current = rc.tod().get_current_time(tod_schedule_hex);
1318  int i = 0;
1319  for (const time_of_day& tod : schedule) {
1320  if (i == current) {
1321  tooltip << markup::tag("big", markup::bold(tod.name)) << "\n";
1322  } else {
1323  tooltip << tod.name << "\n";
1324  }
1325  i++;
1326  }
1327 
1328  int times = schedule.size();
1329  text << current + 1 << "/" << times;
1330 
1331  return text_report(text.str(), tooltip.str(), "..schedule");
1332 }
1333 REPORT_GENERATOR(tod_stats, rc)
1334 {
1335  map_location mouseover_hex = rc.screen().mouseover_hex();
1336  if (mouseover_hex.valid()) return tod_stats_at(rc, mouseover_hex);
1337  return tod_stats_at(rc, rc.screen().selected_hex());
1338 }
1339 REPORT_GENERATOR(selected_tod_stats, rc)
1340 {
1341  const unit *u = get_selected_unit(rc);
1342  if(!u) return tod_stats_at(rc, map_location::null_location());
1343  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1344  const map_location& hex =
1345  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1346  return tod_stats_at(rc, hex);
1347 }
1348 
1349 static config time_of_day_at(const reports::context& rc, const map_location& mouseover_hex)
1350 {
1351  std::ostringstream tooltip;
1352  time_of_day tod = get_visible_time_of_day_at(rc, mouseover_hex);
1353 
1354  int b = tod.lawful_bonus;
1355  int l = generic_combat_modifier(b, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1356  std::string lawful_color("white");
1357  std::string chaotic_color("white");
1358  std::string liminal_color("white");
1359 
1360  if (b != 0) {
1361  lawful_color = (b > 0) ? "#0f0" : "#f00";
1362  chaotic_color = (b < 0) ? "#0f0" : "#f00";
1363  }
1364  if (l != 0) {
1365  liminal_color = (l > 0) ? "#0f0" : "#f00";
1366  }
1367  tooltip << _("Time of day:") << " " << markup::bold(tod.name) << "\n"
1368  << _("Lawful units:") << " "
1369  << markup::span_color(lawful_color, utils::signed_percent(b)) << "\n"
1370  << _("Neutral units:") << " " << utils::signed_percent(0) << '\n'
1371  << _("Chaotic units:") << " "
1372  << markup::span_color(chaotic_color, utils::signed_percent(-b)) << "\n"
1373  << _("Liminal units:") << " "
1374  << markup::span_color(liminal_color, utils::signed_percent(l)) << "\n";
1375 
1376  std::string tod_image = tod.image;
1377  if(tod.bonus_modified > 0) {
1378  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_bright << ")").str();
1379  } else if(tod.bonus_modified < 0) {
1380  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_dark << ")").str();
1381  }
1382 
1383  return image_report(tod_image, tooltip.str(), "time_of_day_" + tod.id);
1384 }
1386 {
1387  map_location mouseover_hex = rc.screen().mouseover_hex();
1388  if (mouseover_hex.valid()) return time_of_day_at(rc, mouseover_hex);
1389  return time_of_day_at(rc, rc.screen().selected_hex());
1390 }
1391 REPORT_GENERATOR(selected_time_of_day, rc)
1392 {
1393  const unit *u = get_selected_unit(rc);
1394  if(!u) return time_of_day_at(rc, map_location::null_location());
1395  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1396  const map_location& hex =
1397  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1398  return time_of_day_at(rc, hex);
1399 }
1400 
1401 static config unit_box_at(const reports::context& rc, const map_location& mouseover_hex)
1402 {
1403  std::ostringstream tooltip;
1404  time_of_day global_tod = rc.tod().get_time_of_day();
1405  time_of_day local_tod = get_visible_time_of_day_at(rc, mouseover_hex);
1406 
1407  int bonus = local_tod.lawful_bonus;
1408  int bonus_lim = generic_combat_modifier(bonus, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1409 
1410  std::string lawful_color("white");
1411  std::string chaotic_color("white");
1412  std::string liminal_color("white");
1413 
1414  if (bonus != 0) {
1415  lawful_color = (bonus > 0) ? "green" : "red";
1416  chaotic_color = (bonus < 0) ? "green" : "red";
1417  }
1418  if (bonus_lim != 0) {
1419  liminal_color = (bonus_lim > 0) ? "green" : "red";
1420  }
1421  tooltip << local_tod.name << '\n'
1422  << _("Lawful units:") << " "
1423  << markup::span_color(lawful_color, utils::signed_percent(bonus)) << "\n"
1424  << _("Neutral units:") << " " << utils::signed_percent(0) << '\n'
1425  << _("Chaotic units:") << " "
1426  << markup::span_color(chaotic_color, utils::signed_percent(-bonus)) << "\n"
1427  << _("Liminal units:") << " "
1428  << markup::span_color(liminal_color, utils::signed_percent(bonus_lim)) << "\n";
1429 
1430  std::string local_tod_image = "themes/classic/" + local_tod.image;
1431  std::string global_tod_image = "themes/classic/" + global_tod.image;
1432  if(local_tod.bonus_modified != 0) {
1433  local_tod_image += "~BLIT(";
1434  if (local_tod.bonus_modified > 0) {
1435  local_tod_image += game_config::images::tod_bright;
1436  } else if (local_tod.bonus_modified < 0) {
1437  local_tod_image += game_config::images::tod_dark;
1438  }
1439  local_tod_image += ")";
1440  }
1441 
1442  const gamemap &map = rc.map();
1443  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1444 
1445  //if (t_translation::terrain_matches(terrain, t_translation::ALL_OFF_MAP))
1446  // return config();
1447 
1448  //if (map.is_keep(mouseover_hex)) {
1449  // add_image(cfg, "icons/terrain/terrain_type_keep.png", "");
1450  //}
1451 
1452  const t_translation::ter_list& underlying_terrains = map.underlying_union_terrain(terrain);
1453 
1454  std::string bg_terrain_image;
1455 
1456  for (const t_translation::terrain_code& underlying_terrain : underlying_terrains) {
1457  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1458  bg_terrain_image = "~BLIT(unit_env/terrain/terrain-" + terrain_id + ".png)" + bg_terrain_image;
1459  }
1460 
1461  std::stringstream color;
1462  color << local_tod.color;
1463 
1464  bg_terrain_image = bg_terrain_image + "~CS(" + color.str() + ")";
1465 
1466  const unit* u = get_visible_unit(rc);
1467  std::string unit_image;
1468  if (u) {
1469  unit_image = "~BLIT(" + u->absolute_image() + u->image_mods() + ",35,22)";
1470  }
1471 
1472  std::string tod_image = global_tod_image + "~BLIT(" + local_tod_image + ")";
1473 
1474  return image_report(tod_image + bg_terrain_image + unit_image, tooltip.str(), "time_of_day");
1475 }
1476 REPORT_GENERATOR(unit_box, rc)
1477 {
1478  map_location mouseover_hex = rc.screen().mouseover_hex();
1479  return unit_box_at(rc, mouseover_hex);
1480 }
1481 
1482 
1484 {
1485  std::ostringstream str, tooltip;
1486  str << rc.tod().turn();
1487  int nb = rc.tod().number_of_turns();
1488  tooltip << _("Turn Number");
1489 
1490  if(nb != -1) {
1491  str << '/' << nb;
1492  tooltip << "\n\n" << _("When the game exceeds the number of turns indicated by the second number, it will end.");
1493  }
1494  return text_report(str.str(), tooltip.str());
1495 }
1496 
1498 {
1499  std::ostringstream str;
1500  const team& viewing_team = rc.screen().viewing_team();
1501  // Suppose the full unit map is applied.
1502  int fake_gold = viewing_team.gold();
1503  if (rc.wb()) {
1504  fake_gold -= rc.wb()->get_spent_gold_for(viewing_team.side());
1505  }
1506 
1507  if (!rc.screen().viewing_team_is_playing()) {
1509  } else if (fake_gold < 0) {
1511  } else {
1512  str << utils::half_signed_value(fake_gold);
1513  }
1514 
1515  return text_report(str.str(), _("Gold") + "\n\n" + _("The amount of gold currently available to recruit and maintain your army."));
1516 }
1517 
1518 REPORT_GENERATOR(villages, rc)
1519 {
1520  std::ostringstream str;
1521  const team &viewing_team = rc.screen().viewing_team();
1522  str << viewing_team.villages().size() << '/';
1523  if (viewing_team.uses_shroud()) {
1524  int unshrouded_villages = 0;
1525  for (const map_location &loc : rc.map().villages()) {
1526  if (!viewing_team.shrouded(loc))
1527  ++unshrouded_villages;
1528  }
1529  str << unshrouded_villages;
1530  } else {
1531  str << rc.map().villages().size();
1532  }
1533  return gray_inactive(rc,str.str(), _("Villages") + "\n\n" + _("The fraction of known villages that your side has captured."));
1534 }
1535 
1536 REPORT_GENERATOR(num_units, rc)
1537 {
1538  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."));
1539 }
1540 
1541 REPORT_GENERATOR(upkeep, rc)
1542 {
1543  std::ostringstream str;
1544  const team& viewing_team = rc.screen().viewing_team();
1545  team_data td(rc.dc(), viewing_team);
1546  str << td.expenses << " (" << td.upkeep << ")";
1547  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."));
1548 }
1549 
1550 REPORT_GENERATOR(expenses, rc)
1551 {
1552  const team& viewing_team = rc.screen().viewing_team();
1553  team_data td(rc.dc(), viewing_team);
1554  return gray_inactive(rc,std::to_string(td.expenses));
1555 }
1556 
1557 REPORT_GENERATOR(income, rc)
1558 {
1559  std::ostringstream str;
1560  const team& viewing_team = rc.screen().viewing_team();
1561  team_data td(rc.dc(), viewing_team);
1562 
1563  if (!rc.screen().viewing_team_is_playing()) {
1564  if (td.net_income < 0) {
1565  td.net_income = - td.net_income;
1567  } else {
1569  }
1570  } else if (td.net_income < 0) {
1571  td.net_income = - td.net_income;
1573  } else {
1574  str << td.net_income;
1575  }
1576 
1577  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."));
1578 }
1579 
1580 namespace {
1581 void blit_tced_icon(config &cfg, const std::string &terrain_id, const std::string &icon_image, bool high_res,
1582  const std::string &terrain_name) {
1583  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
1584  const std::string terrain_image = "terrain/" + icon_image + (high_res ? "_30.png" : ".png");
1585  add_image(cfg, tc_base + "~RC(magenta>" + terrain_id + ")~BLIT(" + terrain_image + ")", terrain_name);
1586 }
1587 }
1588 
1589 REPORT_GENERATOR(terrain_info, rc)
1590 {
1591  const gamemap& map = rc.map();
1592  map_location mouseover_hex = rc.screen().mouseover_hex();
1593 
1594  if(!map.on_board(mouseover_hex)) {
1595  mouseover_hex = rc.screen().selected_hex();
1596  }
1597 
1598  if(!map.on_board(mouseover_hex)) {
1599  return config();
1600  }
1601 
1602  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1604  return config();
1605  }
1606 
1607  config cfg;
1608 
1609  bool high_res = false;
1610 
1611  if(display::get_singleton()->shrouded(mouseover_hex)) {
1612  return cfg;
1613  }
1614  //TODO
1615 // if (display::get_singleton()->fogged(mouseover_hex)) {
1616 // blit_tced_icon(cfg, "fog", high_res);
1617 // }
1618 //
1619 // if (map.is_keep(mouseover_hex)) {
1620 // blit_tced_icon(cfg, "keep", high_res);
1621 // }
1622 
1623  const t_translation::ter_list& underlying_terrains = map.underlying_union_terrain(terrain);
1624  for(const t_translation::terrain_code& underlying_terrain : underlying_terrains) {
1626  continue;
1627  }
1628  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1629  const std::string& terrain_name = map.get_terrain_string(underlying_terrain);
1630  const std::string& terrain_icon = map.get_terrain_info(underlying_terrain).icon_image();
1631  if(terrain_icon.empty()) {
1632  continue;
1633  }
1634  blit_tced_icon(cfg, terrain_id, terrain_icon, high_res, terrain_name);
1635  }
1636 
1637  if(map.is_village(mouseover_hex)) {
1638  int owner = rc.dc().village_owner(mouseover_hex);
1639  // This report is used in both game and editor. get_team(viewing_side) would throw in the editor's
1640  // terrain-only mode, but if the village already has an owner then we're not in that mode.
1641  if(owner != 0) {
1642  const team& viewing_team = rc.screen().viewing_team();
1643 
1644  if(!viewing_team.fogged(mouseover_hex)) {
1645  const team& owner_team = rc.dc().get_team(owner);
1646 
1647  std::string flag_icon = owner_team.flag_icon();
1648  std::string old_rgb = game_config::flag_rgb;
1649  std::string new_rgb = team::get_side_color_id(owner_team.side());
1650  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1651  if(flag_icon.empty()) {
1653  }
1654  std::string tooltip = side_tooltip(owner_team);
1655  std::string side = std::to_string(owner_team.side());
1656 
1657  add_image(cfg, flag_icon + mods, tooltip);
1658  add_text(cfg, side, tooltip);
1659  }
1660  }
1661  }
1662 
1663  return cfg;
1664 }
1665 
1666 REPORT_GENERATOR(terrain, rc)
1667 {
1668  const gamemap &map = rc.map();
1669  const team& viewing_team = rc.screen().viewing_team();
1670  map_location mouseover_hex = rc.screen().mouseover_hex();
1671  if (!map.on_board(mouseover_hex) || viewing_team.shrouded(mouseover_hex))
1672  return config();
1673 
1674  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1676  return config();
1677 
1678  std::ostringstream str;
1679  if (map.is_village(mouseover_hex))
1680  {
1681  int owner = rc.dc().village_owner(mouseover_hex);
1682  if (owner == 0 || viewing_team.fogged(mouseover_hex)) {
1683  str << map.get_terrain_info(terrain).income_description();
1684  } else if (owner == viewing_team.side()) {
1685  str << map.get_terrain_info(terrain).income_description_own();
1686  } else if (viewing_team.is_enemy(owner)) {
1687  str << map.get_terrain_info(terrain).income_description_enemy();
1688  } else {
1689  str << map.get_terrain_info(terrain).income_description_ally();
1690  }
1691 
1692  const std::string& underlying_desc = map.get_underlying_terrain_string(terrain);
1693  if(!underlying_desc.empty()) {
1694  str << underlying_desc;
1695  }
1696  } else {
1697  str << map.get_terrain_string(terrain);
1698  }
1699 
1700  return text_report(str.str());
1701 }
1702 
1703 REPORT_GENERATOR(zoom_level, rc)
1704 {
1705  std::ostringstream text;
1706  std::ostringstream tooltip;
1707  std::ostringstream help;
1708 
1709  text << static_cast<int>(rc.screen().get_zoom_factor() * 100) << "%";
1710 
1711  return text_report(text.str(), tooltip.str(), help.str());
1712 }
1713 
1714 REPORT_GENERATOR(position, rc)
1715 {
1716  const gamemap &map = rc.map();
1717  map_location mouseover_hex = rc.screen().mouseover_hex(),
1718  displayed_unit_hex = rc.screen().displayed_unit_hex(),
1719  selected_hex = rc.screen().selected_hex();
1720 
1721  if (!map.on_board(mouseover_hex)) {
1722  if (!map.on_board(selected_hex))
1723  return config();
1724  else {
1725  mouseover_hex = selected_hex;
1726  }
1727  }
1728 
1729  t_translation::terrain_code terrain = map[mouseover_hex];
1731  return config();
1732 
1733  std::ostringstream str;
1734  str << mouseover_hex;
1735 
1736  const unit *u = get_visible_unit(rc);
1737  const team &viewing_team = rc.screen().viewing_team();
1738  if (!u ||
1739  (displayed_unit_hex != mouseover_hex
1740  && displayed_unit_hex != rc.screen().selected_hex())
1741  || viewing_team.shrouded(mouseover_hex))
1742  {
1743  return text_report(str.str());
1744  }
1745 
1746  int move_cost = u->movement_cost(terrain);
1747  int defense = 100 - u->defense_modifier(terrain);
1748  if (move_cost < movetype::UNREACHABLE) {
1749  str << " " << defense << "%," << move_cost;
1750  } else if (mouseover_hex == displayed_unit_hex) {
1751  str << " " << defense << "%,‒";
1752  } else {
1753  str << " ‒";
1754  }
1755  return text_report(str.str());
1756 }
1757 
1758 REPORT_GENERATOR(side_playing, rc)
1759 {
1760  const team &active_team = rc.screen().playing_team();
1761  std::string flag_icon = active_team.flag_icon();
1762  std::string old_rgb = game_config::flag_rgb;
1763  std::string new_rgb = team::get_side_color_id(rc.screen().playing_team().side());
1764  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1765  if (flag_icon.empty()) {
1767  }
1768  return image_report(flag_icon + mods, side_tooltip(active_team));
1769 }
1770 
1771 REPORT_GENERATOR(observers, rc)
1772 {
1773  const std::set<std::string> &observers = rc.screen().observers();
1774  if (observers.empty())
1775  return config();
1776 
1777  std::ostringstream str;
1778  str << _("Observers:") << '\n';
1779  for (const std::string &obs : observers) {
1780  str << obs << '\n';
1781  }
1782  return image_report(game_config::images::observer, str.str());
1783 }
1784 
1785 REPORT_GENERATOR(report_clock, /*rc*/)
1786 {
1787  config report;
1789 
1791  ? "%I:%M %p"
1792  : "%H:%M";
1793 
1794  const auto now = std::chrono::system_clock::now();
1795  add_text(report, chrono::format_local_timestamp(now, format), _("Clock"));
1796 
1797  return report;
1798 }
1799 
1800 
1801 REPORT_GENERATOR(battery, /*rc*/)
1802 {
1803  config report;
1804 
1806  add_text(report, (boost::format("%.0f %%") % desktop::battery_info::get_battery_percentage()).str(), _("Battery"));
1807 
1808  return report;
1809 }
1810 
1811 REPORT_GENERATOR(report_countdown, rc)
1812 {
1813  using namespace std::chrono_literals;
1814  const team& viewing_team = rc.screen().viewing_team();
1815  if (viewing_team.countdown_time() == 0ms) {
1816  return report_report_clock(rc);
1817  }
1818 
1819  std::ostringstream time_str, formatted_time_str;
1820 
1821  using std::chrono::duration_cast;
1822 #ifdef __cpp_lib_format
1823  auto sec = duration_cast<std::chrono::seconds>(viewing_team.countdown_time());
1824  time_str << std::format("{:%M:%S}", sec);
1825 #else
1826  auto fmt = [](const auto& duration) -> std::string {
1827  return formatter{} << std::setw(2) << std::setfill('0') << duration.count();
1828  };
1829 
1830  // Create the time string
1831  auto sec = duration_cast<std::chrono::seconds>(viewing_team.countdown_time());
1832  auto min = duration_cast<std::chrono::minutes>(sec);
1833  time_str << fmt(min) << ':' << fmt(sec - min);
1834 #endif
1835 
1836  // Colorize the time string
1837  if (!rc.screen().viewing_team_is_playing()) {
1838  formatted_time_str << span_color(font::GRAY_COLOR, time_str.str());
1839  } else if (sec < 60s) {
1840  formatted_time_str << span_color("#c80000", time_str.str());
1841  } else if (sec < 120s) {
1842  formatted_time_str << span_color("#c8c800", time_str.str());
1843  } else {
1844  formatted_time_str << time_str.str();
1845  }
1846 
1847  config report;
1849  add_text(report, formatted_time_str.str(),
1850  _("Turn Countdown") + "\n\n" + _("Countdown until your turn automatically ends."));
1851 
1852  return report;
1853 }
1854 
1855 void reports::register_generator(const std::string &name, reports::generator *g)
1856 {
1857  dynamic_generators_[name].reset(g);
1858  all_reports_.clear();
1859 }
1860 
1861 config reports::generate_report(const std::string &name, const reports::context& rc, bool only_static)
1862 {
1863  if (!only_static) {
1864  dynamic_report_generators::const_iterator i = dynamic_generators_.find(name);
1865  if (i != dynamic_generators_.end())
1866  return i->second->generate(rc);
1867  }
1868  static_report_generators::const_iterator j = static_generators.find(name);
1869  if (j != static_generators.end()) {
1870  return j->second(rc);
1871  }
1872  return config();
1873 }
1874 
1875 const std::set<std::string> &reports::report_list()
1876 {
1877  if (!all_reports_.empty()) return all_reports_;
1878  for (const static_report_generators::value_type &v : static_generators) {
1879  all_reports_.insert(v.first);
1880  }
1881  for (const dynamic_report_generators::value_type &v : dynamic_generators_) {
1882  all_reports_.insert(v.first);
1883  }
1884  return all_reports_;
1885 }
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:1597
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:1624
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:1604
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: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:300
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:301
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:384
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:1861
void register_generator(const std::string &name, generator *)
Definition: reports.cpp:1855
std::set< std::string > all_reports_
Definition: reports.hpp:90
const std::set< std::string > & report_list()
Definition: reports.cpp:1875
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:201
std::string base_str() const
Definition: tstring.hpp:206
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 const t_string get_side_color_name_for_UI(unsigned side)
Definition: team.cpp:983
static std::string get_side_color_id(unsigned side)
Definition: team.cpp:956
bool shrouded(const map_location &loc) const
Definition: team.cpp:635
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:644
const t_string & income_description_enemy() const
Definition: terrain.hpp:152
const std::string & icon_image() const
Definition: terrain.hpp:44
const t_string & income_description() const
Definition: terrain.hpp:150
const t_string & income_description_ally() const
Definition: terrain.hpp:151
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:153
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:796
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:719
This class represents a single unit of a specific type.
Definition: unit.hpp:132
@ selected_hex
Image on the selected unit.
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
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
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:358
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2611
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:520
unit_alignments::type alignment() const
The alignment of this unit.
Definition: unit.hpp:490
int level() const
The current level of this unit.
Definition: unit.hpp:574
const t_string & type_name() const
Gets the translatable name of this unit's type.
Definition: unit.hpp:368
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:514
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1423
std::string small_profile() const
An optional profile image to display in Help.
Definition: unit.cpp:1164
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1958
const unit_race * race() const
Gets this unit's race.
Definition: unit.hpp:508
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:354
int experience() const
The current number of experience points this unit has.
Definition: unit.hpp:538
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:379
int side() const
The side this unit belongs to.
Definition: unit.hpp:342
std::vector< t_string > unit_special_notes() const
The unit's special notes.
Definition: unit.cpp:2864
int max_experience() const
The max number of experience points this unit can have.
Definition: unit.hpp:544
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:480
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:402
t_string unit_description() const
A detailed description of this unit.
Definition: unit.hpp:465
@ STATE_SLOWED
Definition: unit.hpp:868
@ STATE_INVULNERABLE
The unit is a guardian - it won't move unless a target is sighted.
Definition: unit.hpp:875
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:870
@ STATE_UNHEALABLE
The unit has not moved.
Definition: unit.hpp:873
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:869
std::vector< std::pair< std::string, std::string > > amla_icons() const
Gets the image and description data for modification advancements.
Definition: unit.cpp:1870
bool can_advance() const
Checks whether this unit has any options to advance to.
Definition: unit.hpp:271
std::map< std::string, std::string > advancement_icons() const
Gets and image path and and associated description for each advancement option.
Definition: unit.cpp:1830
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1754
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:941
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:1818
utils::string_map_res get_base_resistances() const
Gets resistances without any abilities applied.
Definition: unit.hpp:1072
int max_attacks() const
The maximum number of attacks this unit may perform per turn, usually 1.
Definition: unit.hpp:992
int attacks_left() const
Gets the remaining number of attacks this unit can perform this turn.
Definition: unit.hpp:1008
color_t xp_color() const
Color for this unit's XP.
Definition: unit.cpp:1252
color_t hp_color() const
Color for this unit's current hitpoints.
Definition: unit.cpp:1208
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2769
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2588
int jamming() const
Gets the unit's jamming points.
Definition: unit.hpp:1480
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1431
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit's movement cost on a particular terrain.
Definition: unit.hpp:1514
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1356
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1340
int vision() const
Gets the unit's vision points.
Definition: unit.hpp:1474
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:1127
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1302
const std::vector< t_string > & trait_descriptions() const
Gets the descriptions of the currently registered traits.
Definition: unit.hpp:1116
const std::vector< t_string > & trait_names() const
Gets the names of the currently registered traits.
Definition: unit.hpp:1106
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
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:80
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
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 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:70
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:840
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: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: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 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:774
static config unit_type(const unit *u)
Definition: reports.cpp:191
static std::string side_tooltip(const team &team)
Definition: reports.cpp:237
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:1308
static unit_const_ptr get_selected_unit_ptr(const reports::context &rc)
Definition: reports.cpp:148
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:1071
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:768
static config unit_traits(const unit *u)
Definition: reports.cpp:307
static config unit_box_at(const reports::context &rc, const map_location &mouseover_hex)
Definition: reports.cpp:1401
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 unit_abilities(const unit *u, const map_location &loc)
Definition: reports.cpp:416
static config time_of_day_at(const reports::context &rc, const map_location &mouseover_hex)
Definition: reports.cpp:1349
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:1090
#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:666
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:1083
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: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:61
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: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:529
mock_char c
static map_location::direction s
#define b