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