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