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->get_traits_list();
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::pair<std::string, std::string> types = at.damage_type();
876  std::string secondary_lang_type = types.second;
877  if (!secondary_lang_type.empty()) {
878  secondary_lang_type = ", " + string_table["type_" + secondary_lang_type];
879  }
880  std::string lang_type = string_table["type_" + types.first] + secondary_lang_type;
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/") + types.first + ".png~SCALE_INTO(16,16)";
885  const std::string secondary_type_png = !(types.second).empty() ? std::string("icons/profiles/") + types.second + ".png~SCALE_INTO(16,16)" : "";
886  const bool range_png_exists = image::locator(range_png).file_exists();
887  const bool type_png_exists = image::locator(type_png).file_exists();
888  const bool secondary_type_png_exists = image::locator(secondary_type_png).file_exists();
889 
890  if(!range_png_exists || !type_png_exists || (!secondary_type_png_exists && !secondary_lang_type.empty())) {
891  str << span_color(font::weapon_details_color) << " " << " "
892  << range << font::weapon_details_sep
893  << lang_type << "</span>\n";
894  }
895 
896  tooltip << _("Weapon range: ") << "<b>" << range << "</b>\n"
897  << _("Damage type: ") << "<b>" << lang_type << "</b>\n"
898  << _("Damage versus: ") << '\n';
899 
900  // Show this weapon damage and resistance against all the different units.
901  // We want weak resistances (= good damage) first.
902  std::map<int, std::set<std::string>, std::greater<int>> resistances;
903  std::set<std::string> seen_types;
904  const team &unit_team = rc.dc().get_team(u.side());
905  const team &viewing_team = rc.teams()[rc.screen().viewing_team()];
906  for (const unit &enemy : rc.units())
907  {
908  if (enemy.incapacitated()) //we can't attack statues so don't display them in this tooltip
909  continue;
910  if (!unit_team.is_enemy(enemy.side()))
911  continue;
912  const map_location &loc = enemy.get_location();
913  const bool see_all = game_config::debug || rc.screen().show_everything();
914  if (!enemy.is_visible_to_team(viewing_team, see_all))
915  continue;
916  bool new_type = seen_types.insert(enemy.type_id()).second;
917  if (new_type) {
918  int resistance = enemy.resistance_against(at, false, loc);
919  resistances[resistance].insert(enemy.type_name());
920  }
921  }
922 
923  for (const auto& resist : resistances) {
924  int damage_with_resistance = round_damage(specials_damage, damage_multiplier * resist.first, damage_divisor);
925  tooltip << "<b>" << damage_with_resistance << "</b> "
926  << "<span color='" << attack_info_percent_color(resist.first-100).to_hex_string() << "'>"
927  << "<i>(" << utils::signed_percent(resist.first-100) << ")</i>"
928  << naps
929  << " : \t" // spaces to align the tab to a multiple of 8
930  << utils::join(resist.second, " " + font::unicode_bullet + " ") << '\n';
931  }
932  const string_with_tooltip damage_versus {flush(str), flush(tooltip)};
933 
934 #if 0
935  // We wanted to use the attack icon here, but couldn't find a good layout.
936  // The default images are 60x60 and have a 2-pixel transparent border. Trim it.
937  // The first SCALE() accounts for theoretically possible add-ons attack images larger than 60x60.
938  const std::string attack_icon = at.icon() + "~SCALE_INTO_SHARP(60,60)~CROP(2,2,56,56)~SCALE_INTO_SHARP(32,32)";
939  add_image(res, attack_icon, at.name());
940  add_text(res, " ", "");
941 #endif
942 
943  // The icons are 16x16. We add 5px padding for alignment reasons (placement of the icon in relation to ascender and descender letters).
944  const std::string spacer = "misc/blank.png~CROP(0, 0, 16, 21)"; // 21 == 16+5
945  add_image(res, spacer + "~BLIT(" + range_png + ",0,5)", damage_versus.tooltip);
946  add_image(res, spacer + "~BLIT(" + type_png + ",0,5)", damage_versus.tooltip);
947  if(secondary_type_png_exists){
948  add_image(res, spacer + "~BLIT(" + secondary_type_png + ",0,5)", damage_versus.tooltip);
949  }
950  add_text(res, damage_and_num_attacks.str, damage_and_num_attacks.tooltip);
951  add_text(res, damage_versus.str, damage_versus.tooltip); // This string is usually empty
952 
953  const std::string &accuracy_parry = at.accuracy_parry_description();
954  if (!accuracy_parry.empty())
955  {
957  << " " << accuracy_parry << "</span>\n";
958  int accuracy = at.accuracy();
959  if (accuracy) {
960  tooltip << _("Accuracy:") << "<b>"
961  << utils::signed_percent(accuracy) << "</b>\n";
962  }
963  int parry = at.parry();
964  if (parry) {
965  tooltip << _("Parry:") << "<b>"
966  << utils::signed_percent(parry) << "</b>\n";
967  }
968  add_text(res, flush(str), flush(tooltip));
969  }
970  }
971 
972  {
973  //If we have a second unit, do the 2-unit specials_context
974  bool attacking = (u.side() == rc.screen().playing_side());
975  auto ctx = (sec_u == nullptr) ? at.specials_context_for_listing(attacking) :
976  at.specials_context(u.shared_from_this(), sec_u->shared_from_this(), hex, sec_u->get_location(), attacking, sec_u_weapon);
977 
978  boost::dynamic_bitset<> active;
979  const std::vector<std::pair<t_string, t_string>> &specials = at.special_tooltips(&active);
980  const std::size_t specials_size = specials.size();
981  for ( std::size_t i = 0; i != specials_size; ++i )
982  {
983  // Aliases for readability:
984  const t_string &name = specials[i].first;
985  const t_string &description = specials[i].second;
986  const color_t &details_color = active[i] ? font::weapon_details_color :
988 
989  str << span_color(details_color) << " " << " " << name << naps << '\n';
990  std::string help_page = "weaponspecial_" + name.base_str();
991  tooltip << _("Weapon special: ") << "<b>" << name << "</b>";
992  if ( !active[i] )
993  tooltip << "<i>" << _(" (inactive)") << "</i>";
994  tooltip << '\n' << description;
995 
996  add_text(res, flush(str), flush(tooltip), help_page);
997  }
998  if(!specials.empty()) {
999  // Add some padding so the end of the specials list
1000  // isn't too close vertically to the attack icons of
1001  // the next attack. Also for symmetry with the padding
1002  // above the list of specials (below the attack icon line).
1003  const std::string spacer = "misc/blank.png~CROP(0, 0, 1, 5)";
1004  add_image(res, spacer, "");
1005  add_text(res, "\n", "");
1006  }
1007  }
1008  return damage;
1009 }
1010 
1011 // Conversion routine for both unscathed and damage change percentage.
1012 static std::string format_prob(double prob)
1013 {
1014  if(prob > 0.9995) {
1015  return "100%";
1016  } else if(prob < 0.0005) {
1017  return "0%";
1018  }
1019  std::ostringstream res;
1020  res << std::setprecision(prob < 0.01 ? 1 : prob < 0.1 ? 2 : 3) << 100.0 * prob << "%";
1021  return res.str();
1022 }
1023 
1024 static std::string format_hp(unsigned hp)
1025 {
1026  std::ostringstream res;
1027  res << ' ' << std::setw(3) << hp;
1028  return res.str();
1029 }
1030 
1031 static config unit_weapons(const reports::context& rc, unit_const_ptr attacker, const map_location &attacker_pos, const unit *defender, bool show_attacker)
1032 {
1033  if (!attacker || !defender) return config();
1034 
1035  const unit* u = show_attacker ? attacker.get() : defender;
1036  const unit* sec_u = !show_attacker ? attacker.get() : defender;
1037  const map_location unit_loc = show_attacker ? attacker_pos : defender->get_location();
1038 
1039  std::ostringstream str, tooltip;
1040  config res;
1041 
1042  std::vector<battle_context> weapons;
1043  for (unsigned int i = 0; i < attacker->attacks().size(); i++) {
1044  // skip weapons with attack_weight=0
1045  if (attacker->attacks()[i].attack_weight() > 0) {
1046  weapons.emplace_back(rc.units(), attacker_pos, defender->get_location(), i, -1, 0.0, nullptr, attacker);
1047  }
1048  }
1049 
1050  for (const battle_context& weapon : weapons) {
1051 
1052  // Predict the battle outcome.
1053  combatant attacker_combatant(weapon.get_attacker_stats());
1054  combatant defender_combatant(weapon.get_defender_stats());
1055  attacker_combatant.fight(defender_combatant);
1056 
1057  const battle_context_unit_stats& context_unit_stats =
1058  show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1059  const battle_context_unit_stats& other_context_unit_stats =
1060  !show_attacker ? weapon.get_attacker_stats() : weapon.get_defender_stats();
1061 
1062  int total_damage = 0;
1063  int base_damage = 0;
1064  int num_blows = 0;
1065  int chance_to_hit = 0;
1066  t_string weapon_name = _("weapon^None");
1067 
1068  color_t dmg_color = font::weapon_color;
1069  if (context_unit_stats.weapon) {
1070  base_damage = attack_info(rc, *context_unit_stats.weapon, res, *u, unit_loc, sec_u, other_context_unit_stats.weapon);
1071  total_damage = context_unit_stats.damage;
1072  num_blows = context_unit_stats.num_blows;
1073  chance_to_hit = context_unit_stats.chance_to_hit;
1074  weapon_name = context_unit_stats.weapon->name();
1075 
1076  if ( total_damage > base_damage )
1077  dmg_color = font::good_dmg_color;
1078  else if ( total_damage < base_damage )
1079  dmg_color = font::bad_dmg_color;
1080  } else {
1081  str << span_color(font::weapon_color) << weapon_name << naps << "\n";
1082  tooltip << _("Weapon: ") << "<b>" << weapon_name << "</b>\n"
1083  << _("Damage: ") << "<b>" << "0" << "</b>\n";
1084  }
1085 
1086  color_t chance_color = game_config::red_to_green(chance_to_hit);
1087 
1088  // Total damage.
1089  str << " " << span_color(dmg_color) << total_damage << naps << span_color(font::weapon_color)
1090  << font::unicode_en_dash << num_blows
1091  << " (" << span_color(chance_color) << chance_to_hit << "%" << naps << ")"
1092  << naps << "\n";
1093 
1094  tooltip << _("Weapon: ") << "<b>" << weapon_name << "</b>\n"
1095  << _("Total damage") << "<b>" << total_damage << "</b>\n";
1096 
1097  // Create the hitpoints distribution.
1098  std::vector<std::pair<int, double>> hp_prob_vector;
1099 
1100  // First, we sort the probabilities in ascending order.
1101  std::vector<std::pair<double, int>> prob_hp_vector;
1102  int i;
1103 
1104  combatant* c = show_attacker ? &attacker_combatant : &defender_combatant;
1105 
1106  for(i = 0; i < static_cast<int>(c->hp_dist.size()); i++) {
1107  double prob = c->hp_dist[i];
1108 
1109  // We keep only values above 0.1%.
1110  if(prob > 0.001)
1111  prob_hp_vector.emplace_back(prob, i);
1112  }
1113 
1114  std::sort(prob_hp_vector.begin(), prob_hp_vector.end());
1115 
1116  //TODO fendrin -- make that dynamically
1117  int max_hp_distrib_rows_ = 10;
1118 
1119  // We store a few of the highest probability hitpoint values.
1120  int nb_elem = std::min<int>(max_hp_distrib_rows_, prob_hp_vector.size());
1121 
1122  for(i = prob_hp_vector.size() - nb_elem;
1123  i < static_cast<int>(prob_hp_vector.size()); i++) {
1124 
1125  hp_prob_vector.emplace_back(prob_hp_vector[i].second, prob_hp_vector[i].first);
1126  }
1127 
1128  // Then, we sort the hitpoint values in ascending order.
1129  std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
1130  // And reverse the order. Might be doable in a better manor.
1131  std::reverse(hp_prob_vector.begin(), hp_prob_vector.end());
1132 
1133  for(i = 0; i < static_cast<int>(hp_prob_vector.size()); i++) {
1134 
1135  int hp = hp_prob_vector[i].first;
1136  double prob = hp_prob_vector[i].second;
1137  color_t prob_color = game_config::blue_to_white(prob * 100.0, true);
1138 
1139  str << span_color(font::weapon_details_color) << " " << " "
1140  << span_color(u->hp_color(hp)) << format_hp(hp) << naps
1141  << " " << font::weapon_numbers_sep << " "
1142  << span_color(prob_color) << format_prob(prob) << naps
1143  << naps << "\n";
1144  }
1145 
1146  add_text(res, flush(str), flush(tooltip));
1147  }
1148  return res;
1149 }
1150 
1151 /*
1152  * Display the attacks of the displayed unit against the unit passed as argument.
1153  * 'hex' is the location the attacker will be at during combat.
1154  */
1155 static config unit_weapons(const reports::context& rc, const unit *u, const map_location &hex)
1156 {
1157  config res = config();
1158  if ((u != nullptr) && (!u->attacks().empty())) {
1159  const std::string attack_headline = _n("Attack", "Attacks", u->attacks().size());
1160 
1162  + attack_headline + "</span>" + '\n', "");
1163 
1164  const auto left = u->attacks_left(false), max = u->max_attacks();
1165  if(max != 1) {
1166  // TRANSLATORS: This string is shown in the sidebar beneath the word "Attacks" when a unit can attack multiple times per turn
1167  const std::string line = VGETTEXT("Remaining: $left/$max",
1168  {{"left", std::to_string(left)},
1169  {"max", std::to_string(max)}});
1170  add_text(res, " " + span_color(font::weapon_details_color, line) + "\n",
1171  _("This unit can attack multiple times per turn."));
1172  }
1173 
1174  for (const attack_type &at : u->attacks())
1175  {
1176  attack_info(rc, at, res, *u, hex);
1177  }
1178  }
1179  return res;
1180 }
1182 {
1183  const unit *u = get_visible_unit(rc);
1184  const map_location& mouseover_hex = rc.screen().mouseover_hex();
1185  const map_location& displayed_unit_hex = rc.screen().displayed_unit_hex();
1186  const map_location& hex = mouseover_hex.valid() ? mouseover_hex : displayed_unit_hex;
1187  if (!u) return config();
1188 
1189  return unit_weapons(rc, u, hex);
1190 }
1191 REPORT_GENERATOR(highlighted_unit_weapons, rc)
1192 {
1194  const unit *sec_u = get_visible_unit(rc);
1195 
1196  if (!u) return report_unit_weapons(rc);
1197  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1198 
1199  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1200  map_location attack_loc;
1201  if (rc.mhb())
1202  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1203 
1204  if (!attack_loc.valid())
1205  return unit_weapons(rc, sec_u, rc.screen().mouseover_hex());
1206 
1207  //TODO: shouldn't this pass sec_u as secodn parameter ?
1208  return unit_weapons(rc, u, attack_loc, sec_u, false);
1209 }
1210 REPORT_GENERATOR(selected_unit_weapons, rc)
1211 {
1213  const unit *sec_u = get_visible_unit(rc);
1214 
1215  if (!u) return config();
1216  if (!sec_u || u.get() == sec_u) return unit_weapons(rc, u.get(), u->get_location());
1217 
1218  map_location highlighted_hex = rc.screen().displayed_unit_hex();
1219  map_location attack_loc;
1220  if (rc.mhb())
1221  attack_loc = rc.mhb()->current_unit_attacks_from(highlighted_hex);
1222 
1223  if (!attack_loc.valid())
1224  return unit_weapons(rc, u.get(), u->get_location());
1225 
1226  return unit_weapons(rc, u, attack_loc, sec_u, true);
1227 }
1228 
1229 REPORT_GENERATOR(unit_image,rc)
1230 {
1231  const unit *u = get_visible_unit(rc);
1232  if (!u) return config();
1233  return image_report(u->absolute_image() + u->image_mods());
1234 }
1235 REPORT_GENERATOR(selected_unit_image, rc)
1236 {
1237  const unit *u = get_selected_unit(rc);
1238  if (!u) return config();
1239  return image_report(u->absolute_image() + u->image_mods());
1240 }
1241 
1242 REPORT_GENERATOR(selected_unit_profile, rc)
1243 {
1244  const unit *u = get_selected_unit(rc);
1245  if (!u) return config();
1246  return image_report(u->small_profile());
1247 }
1248 REPORT_GENERATOR(unit_profile, rc)
1249 {
1250  const unit *u = get_visible_unit(rc);
1251  if (!u) return config();
1252  return image_report(u->small_profile());
1253 }
1254 
1255 static config tod_stats_at(const reports::context& rc, const map_location& hex)
1256 {
1257  std::ostringstream tooltip;
1258  std::ostringstream text;
1259 
1260  const map_location& tod_schedule_hex = (hex.valid() && !display::get_singleton()->shrouded(hex)) ? hex : map_location::null_location();
1261  const std::vector<time_of_day>& schedule = rc.tod().times(tod_schedule_hex);
1262 
1263  tooltip << _("Time of day schedule:") << " \n";
1264  int current = rc.tod().get_current_time(tod_schedule_hex);
1265  int i = 0;
1266  for (const time_of_day& tod : schedule) {
1267  if (i == current) tooltip << "<big><b>";
1268  tooltip << tod.name << "\n";
1269  if (i == current) tooltip << "</b></big>";
1270  i++;
1271  }
1272 
1273  int times = schedule.size();
1274  text << current + 1 << "/" << times;
1275 
1276  return text_report(text.str(), tooltip.str(), "..schedule");
1277 }
1278 REPORT_GENERATOR(tod_stats, rc)
1279 {
1280  map_location mouseover_hex = rc.screen().mouseover_hex();
1281  if (mouseover_hex.valid()) return tod_stats_at(rc, mouseover_hex);
1282  return tod_stats_at(rc, rc.screen().selected_hex());
1283 }
1284 REPORT_GENERATOR(selected_tod_stats, rc)
1285 {
1286  const unit *u = get_selected_unit(rc);
1287  if(!u) return tod_stats_at(rc, map_location::null_location());
1288  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1289  const map_location& hex =
1290  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1291  return tod_stats_at(rc, hex);
1292 }
1293 
1294 static config time_of_day_at(const reports::context& rc, const map_location& mouseover_hex)
1295 {
1296  std::ostringstream tooltip;
1297  time_of_day tod = get_visible_time_of_day_at(rc, mouseover_hex);
1298 
1299  int b = tod.lawful_bonus;
1300  int l = generic_combat_modifier(b, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1301  std::string lawful_color("white");
1302  std::string chaotic_color("white");
1303  std::string liminal_color("white");
1304 
1305  if (b != 0) {
1306  lawful_color = (b > 0) ? "#0f0" : "#f00";
1307  chaotic_color = (b < 0) ? "#0f0" : "#f00";
1308  }
1309  if (l != 0) {
1310  liminal_color = (l > 0) ? "#0f0" : "#f00";
1311  }
1312  tooltip << _("Time of day:") << " <b>" << tod.name << "</b>\n"
1313  << _("Lawful units: ") << "<span foreground=\"" << lawful_color << "\">"
1314  << utils::signed_percent(b) << "</span>\n"
1315  << _("Neutral units: ") << utils::signed_percent(0) << '\n'
1316  << _("Chaotic units: ") << "<span foreground=\"" << chaotic_color << "\">"
1317  << utils::signed_percent(-b) << "</span>\n"
1318  << _("Liminal units: ") << "<span foreground=\"" << liminal_color << "\">"
1319  << utils::signed_percent(l) << "</span>\n";
1320 
1321  std::string tod_image = tod.image;
1322  if(tod.bonus_modified > 0) {
1323  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_bright << ")").str();
1324  } else if(tod.bonus_modified < 0) {
1325  tod_image += (formatter() << "~BLIT(" << game_config::images::tod_dark << ")").str();
1326  }
1327 
1328  return image_report(tod_image, tooltip.str(), "time_of_day_" + tod.id);
1329 }
1331 {
1332  map_location mouseover_hex = rc.screen().mouseover_hex();
1333  if (mouseover_hex.valid()) return time_of_day_at(rc, mouseover_hex);
1334  return time_of_day_at(rc, rc.screen().selected_hex());
1335 }
1336 REPORT_GENERATOR(selected_time_of_day, rc)
1337 {
1338  const unit *u = get_selected_unit(rc);
1339  if(!u) return time_of_day_at(rc, map_location::null_location());
1340  const map_location& attack_indicator_src = game_display::get_singleton()->get_attack_indicator_src();
1341  const map_location& hex =
1342  attack_indicator_src.valid() ? attack_indicator_src : u->get_location();
1343  return time_of_day_at(rc, hex);
1344 }
1345 
1346 static config unit_box_at(const reports::context& rc, const map_location& mouseover_hex)
1347 {
1348  std::ostringstream tooltip;
1349  time_of_day global_tod = rc.tod().get_time_of_day();
1350  time_of_day local_tod = get_visible_time_of_day_at(rc, mouseover_hex);
1351 
1352  int bonus = local_tod.lawful_bonus;
1353  int l = generic_combat_modifier(bonus, unit_alignments::type::liminal, false, rc.tod().get_max_liminal_bonus());
1354 
1355  std::string lawful_color("white");
1356  std::string chaotic_color("white");
1357  std::string liminal_color("white");
1358 
1359  if (bonus != 0) {
1360  lawful_color = (bonus > 0) ? "green" : "red";
1361  chaotic_color = (bonus < 0) ? "green" : "red";
1362  }
1363  if (l != 0) {
1364  liminal_color = (l > 0) ? "green" : "red";
1365  }
1366  tooltip << local_tod.name << '\n'
1367  << _("Lawful units: ") << "<span foreground=\"" << lawful_color << "\">"
1368  << utils::signed_percent(bonus) << "</span>\n"
1369  << _("Neutral units: ") << utils::signed_percent(0) << '\n'
1370  << _("Chaotic units: ") << "<span foreground=\"" << chaotic_color << "\">"
1371  << utils::signed_percent(-bonus) << "</span>\n"
1372  << _("Liminal units: ") << "<span foreground=\"" << liminal_color << "\">"
1373  << utils::signed_percent(l) << "</span>\n";
1374 
1375  std::string local_tod_image = "themes/classic/" + local_tod.image;
1376  std::string global_tod_image = "themes/classic/" + global_tod.image;
1377  if(local_tod.bonus_modified != 0) {
1378  local_tod_image += "~BLIT(";
1379  if (local_tod.bonus_modified > 0) local_tod_image += game_config::images::tod_bright;
1380  else if (local_tod.bonus_modified < 0) local_tod_image += game_config::images::tod_dark;
1381  local_tod_image += ")";
1382  }
1383 
1384  const gamemap &map = rc.map();
1385  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1386 
1387  //if (t_translation::terrain_matches(terrain, t_translation::ALL_OFF_MAP))
1388  // return config();
1389 
1390  //if (map.is_keep(mouseover_hex)) {
1391  // add_image(cfg, "icons/terrain/terrain_type_keep.png", "");
1392  //}
1393 
1394  const t_translation::ter_list& underlying_terrains = map.underlying_union_terrain(terrain);
1395 
1396  std::string bg_terrain_image;
1397 
1398  for (const t_translation::terrain_code& underlying_terrain : underlying_terrains) {
1399  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1400  bg_terrain_image = "~BLIT(unit_env/terrain/terrain-" + terrain_id + ".png)" + bg_terrain_image;
1401  }
1402 
1403  std::stringstream color;
1404  color << local_tod.color;
1405 
1406  bg_terrain_image = bg_terrain_image + "~CS(" + color.str() + ")";
1407 
1408  const unit *u = get_visible_unit(rc);
1409  std::string unit_image;
1410  if (u)
1411  unit_image = "~BLIT(" + u->absolute_image() + u->image_mods() + ",35,22)";
1412 
1413  std::string tod_image = global_tod_image + "~BLIT(" + local_tod_image + ")";
1414 
1415  return image_report(tod_image + bg_terrain_image + unit_image, tooltip.str(), "time_of_day");
1416 }
1417 REPORT_GENERATOR(unit_box, rc)
1418 {
1419  map_location mouseover_hex = rc.screen().mouseover_hex();
1420  return unit_box_at(rc, mouseover_hex);
1421 }
1422 
1423 
1425 {
1426  std::ostringstream str, tooltip;
1427  str << rc.tod().turn();
1428  int nb = rc.tod().number_of_turns();
1429  if (nb != -1) str << '/' << nb;
1430 
1431  tooltip << _("Turn Number");
1432  if(nb != -1) {
1433  tooltip << "\n\n" << _("When the game exceeds the number of turns indicated by the second number, it will end.");
1434  }
1435  return text_report(str.str(), tooltip.str());
1436 }
1437 
1439 {
1440  std::ostringstream str;
1441  int viewing_side = rc.screen().viewing_side();
1442  // Suppose the full unit map is applied.
1443  int fake_gold = rc.dc().get_team(viewing_side).gold();
1444 
1445  if (rc.wb())
1446  fake_gold -= rc.wb()->get_spent_gold_for(viewing_side);
1447  char const *end = naps;
1448  if (viewing_side != rc.screen().playing_side()) {
1449  str << span_color(font::GRAY_COLOR);
1450  }
1451  else if (fake_gold < 0) {
1452  str << span_color(font::BAD_COLOR);
1453  }
1454  else {
1455  end = "";
1456  }
1457  str << utils::half_signed_value(fake_gold) << end;
1458  return text_report(str.str(), _("Gold") + "\n\n" + _("The amount of gold currently available to recruit and maintain your army."));
1459 }
1460 
1461 REPORT_GENERATOR(villages, rc)
1462 {
1463  std::ostringstream str;
1464  int viewing_side = rc.screen().viewing_side();
1465  const team &viewing_team = rc.dc().get_team(viewing_side);
1466  str << viewing_team.villages().size() << '/';
1467  if (viewing_team.uses_shroud()) {
1468  int unshrouded_villages = 0;
1469  for (const map_location &loc : rc.map().villages()) {
1470  if (!viewing_team.shrouded(loc))
1471  ++unshrouded_villages;
1472  }
1473  str << unshrouded_villages;
1474  } else {
1475  str << rc.map().villages().size();
1476  }
1477  return gray_inactive(rc,str.str(), _("Villages") + "\n\n" + _("The fraction of known villages that your side has captured."));
1478 }
1479 
1480 REPORT_GENERATOR(num_units, rc)
1481 {
1482  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."));
1483 }
1484 
1485 REPORT_GENERATOR(upkeep, rc)
1486 {
1487  std::ostringstream str;
1488  int viewing_side = rc.screen().viewing_side();
1489  const team &viewing_team = rc.dc().get_team(viewing_side);
1490  team_data td(rc.dc(), viewing_team);
1491  str << td.expenses << " (" << td.upkeep << ")";
1492  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."));
1493 }
1494 
1495 REPORT_GENERATOR(expenses, rc)
1496 {
1497  int viewing_side = rc.screen().viewing_side();
1498  const team &viewing_team = rc.dc().get_team(viewing_side);
1499  team_data td(rc.dc(), viewing_team);
1500  return gray_inactive(rc,std::to_string(td.expenses));
1501 }
1502 
1503 REPORT_GENERATOR(income, rc)
1504 {
1505  std::ostringstream str;
1506  int viewing_side = rc.screen().viewing_side();
1507  const team &viewing_team = rc.dc().get_team(viewing_side);
1508  team_data td(rc.dc(), viewing_team);
1509  char const *end = naps;
1510  if (viewing_side != rc.screen().playing_side()) {
1511  if (td.net_income < 0) {
1512  td.net_income = - td.net_income;
1513  str << span_color(font::GRAY_COLOR);
1514  str << font::unicode_minus;
1515  }
1516  else {
1517  str << span_color(font::GRAY_COLOR);
1518  }
1519  }
1520  else if (td.net_income < 0) {
1521  td.net_income = - td.net_income;
1522  str << span_color(font::BAD_COLOR);
1523  str << font::unicode_minus;
1524  }
1525  else {
1526  end = "";
1527  }
1528  str << td.net_income << end;
1529  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."));
1530 }
1531 
1532 namespace {
1533 void blit_tced_icon(config &cfg, const std::string &terrain_id, const std::string &icon_image, bool high_res,
1534  const std::string &terrain_name) {
1535  const std::string tc_base = high_res ? "images/buttons/icon-base-32.png" : "images/buttons/icon-base-16.png";
1536  const std::string terrain_image = "terrain/" + icon_image + (high_res ? "_30.png" : ".png");
1537  add_image(cfg, tc_base + "~RC(magenta>" + terrain_id + ")~BLIT(" + terrain_image + ")", terrain_name);
1538 }
1539 }
1540 
1541 REPORT_GENERATOR(terrain_info, rc)
1542 {
1543  const gamemap& map = rc.map();
1544  map_location mouseover_hex = rc.screen().mouseover_hex();
1545 
1546  if(!map.on_board(mouseover_hex)) {
1547  mouseover_hex = rc.screen().selected_hex();
1548  }
1549 
1550  if(!map.on_board(mouseover_hex)) {
1551  return config();
1552  }
1553 
1554  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1556  return config();
1557  }
1558 
1559  config cfg;
1560 
1561  bool high_res = false;
1562 
1563  if(display::get_singleton()->shrouded(mouseover_hex)) {
1564  return cfg;
1565  }
1566  //TODO
1567 // if (display::get_singleton()->fogged(mouseover_hex)) {
1568 // blit_tced_icon(cfg, "fog", high_res);
1569 // }
1570 //
1571 // if (map.is_keep(mouseover_hex)) {
1572 // blit_tced_icon(cfg, "keep", high_res);
1573 // }
1574 
1575  const t_translation::ter_list& underlying_terrains = map.underlying_union_terrain(terrain);
1576  for(const t_translation::terrain_code& underlying_terrain : underlying_terrains) {
1578  continue;
1579  }
1580  const std::string& terrain_id = map.get_terrain_info(underlying_terrain).id();
1581  const std::string& terrain_name = map.get_terrain_string(underlying_terrain);
1582  const std::string& terrain_icon = map.get_terrain_info(underlying_terrain).icon_image();
1583  if(terrain_icon.empty()) {
1584  continue;
1585  }
1586  blit_tced_icon(cfg, terrain_id, terrain_icon, high_res, terrain_name);
1587  }
1588 
1589  if(map.is_village(mouseover_hex)) {
1590  int owner = rc.dc().village_owner(mouseover_hex);
1591  // This report is used in both game and editor. get_team(viewing_side) would throw in the editor's
1592  // terrain-only mode, but if the village already has an owner then we're not in that mode.
1593  if(owner != 0) {
1594  int viewing_side = rc.screen().viewing_side();
1595  const team& viewing_team = rc.dc().get_team(viewing_side);
1596 
1597  if(!viewing_team.fogged(mouseover_hex)) {
1598  const team& owner_team = rc.dc().get_team(owner);
1599 
1600  std::string flag_icon = owner_team.flag_icon();
1601  std::string old_rgb = game_config::flag_rgb;
1602  std::string new_rgb = team::get_side_color_id(owner_team.side());
1603  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1604  if(flag_icon.empty()) {
1606  }
1607  std::string tooltip = side_tooltip(owner_team);
1608  std::string side = std::to_string(owner_team.side());
1609 
1610  add_image(cfg, flag_icon + mods, tooltip);
1611  add_text(cfg, side, tooltip);
1612  }
1613  }
1614  }
1615 
1616  return cfg;
1617 }
1618 
1619 REPORT_GENERATOR(terrain, rc)
1620 {
1621  const gamemap &map = rc.map();
1622  int viewing_side = rc.screen().viewing_side();
1623  const team &viewing_team = rc.dc().get_team(viewing_side);
1624  map_location mouseover_hex = rc.screen().mouseover_hex();
1625  if (!map.on_board(mouseover_hex) || viewing_team.shrouded(mouseover_hex))
1626  return config();
1627 
1628  t_translation::terrain_code terrain = map.get_terrain(mouseover_hex);
1630  return config();
1631 
1632  std::ostringstream str;
1633  if (map.is_village(mouseover_hex))
1634  {
1635  int owner = rc.dc().village_owner(mouseover_hex);
1636  if (owner == 0 || viewing_team.fogged(mouseover_hex)) {
1637  str << map.get_terrain_info(terrain).income_description();
1638  } else if (owner == viewing_side) {
1639  str << map.get_terrain_info(terrain).income_description_own();
1640  } else if (viewing_team.is_enemy(owner)) {
1641  str << map.get_terrain_info(terrain).income_description_enemy();
1642  } else {
1643  str << map.get_terrain_info(terrain).income_description_ally();
1644  }
1645 
1646  const std::string& underlying_desc = map.get_underlying_terrain_string(terrain);
1647  if(!underlying_desc.empty()) {
1648  str << underlying_desc;
1649  }
1650  } else {
1651  str << map.get_terrain_string(terrain);
1652  }
1653 
1654  return text_report(str.str());
1655 }
1656 
1657 REPORT_GENERATOR(zoom_level, rc)
1658 {
1659  std::ostringstream text;
1660  std::ostringstream tooltip;
1661  std::ostringstream help;
1662 
1663  text << static_cast<int>(rc.screen().get_zoom_factor() * 100) << "%";
1664 
1665  return text_report(text.str(), tooltip.str(), help.str());
1666 }
1667 
1668 REPORT_GENERATOR(position, rc)
1669 {
1670  const gamemap &map = rc.map();
1671  map_location mouseover_hex = rc.screen().mouseover_hex(),
1672  displayed_unit_hex = rc.screen().displayed_unit_hex(),
1673  selected_hex = rc.screen().selected_hex();
1674 
1675  if (!map.on_board(mouseover_hex)) {
1676  if (!map.on_board(selected_hex))
1677  return config();
1678  else {
1679  mouseover_hex = selected_hex;
1680  }
1681  }
1682 
1683  t_translation::terrain_code terrain = map[mouseover_hex];
1685  return config();
1686 
1687  std::ostringstream str;
1688  str << mouseover_hex;
1689 
1690  const unit *u = get_visible_unit(rc);
1691  const team &viewing_team = rc.teams()[rc.screen().viewing_team()];
1692  if (!u ||
1693  (displayed_unit_hex != mouseover_hex &&
1694  displayed_unit_hex != rc.screen().selected_hex()) ||
1695  viewing_team.shrouded(mouseover_hex))
1696  return text_report(str.str());
1697 
1698  int move_cost = u->movement_cost(terrain);
1699  int defense = 100 - u->defense_modifier(terrain);
1700  if (move_cost < movetype::UNREACHABLE) {
1701  str << " " << defense << "%," << move_cost;
1702  } else if (mouseover_hex == displayed_unit_hex) {
1703  str << " " << defense << "%,‒";
1704  } else {
1705  str << " ‒";
1706  }
1707  return text_report(str.str());
1708 }
1709 
1710 REPORT_GENERATOR(side_playing, rc)
1711 {
1712  const team &active_team = rc.teams()[rc.screen().playing_team()];
1713  std::string flag_icon = active_team.flag_icon();
1714  std::string old_rgb = game_config::flag_rgb;
1715  std::string new_rgb = team::get_side_color_id(rc.screen().playing_side());
1716  std::string mods = "~RC(" + old_rgb + ">" + new_rgb + ")";
1717  if (flag_icon.empty())
1719  return image_report(flag_icon + mods, side_tooltip(active_team));
1720 }
1721 
1722 REPORT_GENERATOR(observers, rc)
1723 {
1724  const std::set<std::string> &observers = rc.screen().observers();
1725  if (observers.empty())
1726  return config();
1727 
1728  std::ostringstream str;
1729  str << _("Observers:") << '\n';
1730  for (const std::string &obs : observers) {
1731  str << obs << '\n';
1732  }
1733  return image_report(game_config::images::observer, str.str());
1734 }
1735 
1736 REPORT_GENERATOR(report_clock, /*rc*/)
1737 {
1738  config report;
1740 
1741  std::ostringstream ss;
1742 
1744  ? "%I:%M %p"
1745  : "%H:%M";
1746 
1747  std::time_t t = std::time(nullptr);
1748  ss << std::put_time(std::localtime(&t), format);
1749  add_text(report, ss.str(), _("Clock"));
1750 
1751  return report;
1752 }
1753 
1754 
1755 REPORT_GENERATOR(battery, /*rc*/)
1756 {
1757  config report;
1758 
1760  add_text(report, (boost::format("%.0f %%") % desktop::battery_info::get_battery_percentage()).str(), _("Battery"));
1761 
1762  return report;
1763 }
1764 
1765 REPORT_GENERATOR(report_countdown, rc)
1766 {
1767  int viewing_side = rc.screen().viewing_side();
1768  const team &viewing_team = rc.dc().get_team(viewing_side);
1769  int min, sec;
1770  if (viewing_team.countdown_time() == 0)
1771  return report_report_clock(rc);
1772  std::ostringstream str;
1773  sec = viewing_team.countdown_time() / 1000;
1774  char const *end = naps;
1775  if (viewing_side != rc.screen().playing_side())
1776  str << span_color(font::GRAY_COLOR);
1777  else if (sec < 60)
1778  str << "<span foreground=\"#c80000\">";
1779  else if (sec < 120)
1780  str << "<span foreground=\"#c8c800\">";
1781  else
1782  end = "";
1783  min = sec / 60;
1784  str << min << ':';
1785  sec = sec % 60;
1786  if (sec < 10) str << '0';
1787  str << sec << end;
1788 
1789  config report;
1791  add_text(report, str.str(), _("Turn Countdown") + "\n\n" + _("Countdown until your turn automatically ends."));
1792 
1793  return report;
1794 }
1795 
1796 void reports::register_generator(const std::string &name, reports::generator *g)
1797 {
1798  dynamic_generators_[name].reset(g);
1799 }
1800 
1801 config reports::generate_report(const std::string &name, const reports::context& rc, bool only_static)
1802 {
1803  if (!only_static) {
1804  dynamic_report_generators::const_iterator i = dynamic_generators_.find(name);
1805  if (i != dynamic_generators_.end())
1806  return i->second->generate(rc);
1807  }
1808  static_report_generators::const_iterator j = static_generators.find(name);
1809  if (j != static_generators.end())
1810  return j->second(rc);
1811  return config();
1812 }
1813 
1814 const std::set<std::string> &reports::report_list()
1815 {
1816  if (!all_reports_.empty()) return all_reports_;
1817  for (const static_report_generators::value_type &v : static_generators) {
1818  all_reports_.insert(v.first);
1819  }
1820  for (const dynamic_report_generators::value_type &v : dynamic_generators_) {
1821  all_reports_.insert(v.first);
1822  }
1823  return all_reports_;
1824 }
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:124
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:116
virtual int playing_side() const
Definition: display.hpp:213
virtual const map_location & displayed_unit_hex() const
Virtual functions shadowed in game_display.
Definition: display.hpp:212
const map_location & selected_hex() const
Definition: display.hpp:300
bool show_everything() const
Definition: display.hpp:97
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:95
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:563
static const int UNREACHABLE
Magic value that signifies a hex is unreachable.
Definition: movetype.hpp:175
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:1801
void register_generator(const std::string &name, generator *)
Definition: reports.cpp:1796
std::set< std::string > all_reports_
Definition: reports.hpp:87
const std::set< std::string > & report_list()
Definition: reports.cpp:1814
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:838
bool show_variations_in_help() const
Whether the unit type has at least one help-visible variation.
Definition: types.cpp:761
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:2572
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:1331
std::string small_profile() const
An optional profile image to display in Help.
Definition: unit.cpp:1045
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1949
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:2816
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:1861
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:1821
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1749
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:1049
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:1792
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:1160
color_t hp_color() const
Color for this unit's current hitpoints.
Definition: unit.cpp:1106
std::string image_mods() const
Gets an IPF string containing all IPF image mods.
Definition: unit.cpp:2730
std::string absolute_image() const
The name of the file to game_display (used in menus).
Definition: unit.cpp:2549
int jamming() const
Gets the unit's jamming points.
Definition: unit.hpp:1406
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1357
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit's movement cost on a particular terrain.
Definition: unit.hpp:1440
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1282
int total_movement() const
The maximum moves this unit has.
Definition: unit.hpp:1266
int vision() const
Gets the unit's vision points.
Definition: unit.hpp:1400
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1247
std::vector< std::string > get_traits_list() const
Gets a list of the traits this unit currently has.
Definition: unit.cpp:889
const std::vector< t_string > & trait_descriptions() const
Gets the descriptions of the currently registered traits.
Definition: unit.hpp:1093
const std::vector< t_string > & trait_names() const
Gets the names of the currently registered traits.
Definition: unit.hpp:1083
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.
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:83
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:1255
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:1012
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:1031
static config unit_box_at(const reports::context &rc, const map_location &mouseover_hex)
Definition: reports.cpp:1346
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:1294
#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:1024
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:571
mock_char c
static map_location::DIRECTION s
#define b