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