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