The Battle for Wesnoth  1.19.9+dev
unit_attack.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 - 2025
3  by Mark de Wever <koraq@xs4all.nl>
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 
18 
19 #include "color.hpp"
21 #include "gui/widgets/button.hpp"
22 #include "gui/widgets/listbox.hpp"
24 #include "gui/widgets/window.hpp"
25 #include "game_config.hpp"
26 #include "gettext.hpp"
27 #include "language.hpp"
28 #include "serialization/markup.hpp"
29 #include "units/unit.hpp"
30 
31 #include <functional>
32 
33 namespace gui2::dialogs
34 {
35 
37 
38 unit_attack::unit_attack(const unit_map::iterator& attacker_itor,
39  const unit_map::iterator& defender_itor,
40  std::vector<battle_context>&& weapons,
41  const int best_weapon)
42  : modal_dialog(window_id())
43  , selected_weapon_(-1)
44  , attacker_itor_(attacker_itor)
45  , defender_itor_(defender_itor)
46  , weapons_(std::move(weapons))
47  , best_weapon_(best_weapon)
48 {
49 }
50 
52 {
53  const std::size_t index = find_widget<listbox>("weapon_list").get_selected_row();
54  attack_predictions::display(weapons_[index], attacker_itor_.get_shared_ptr(), defender_itor_.get_shared_ptr());
55 }
56 
58 {
60  find_widget<button>("damage_calculation"),
61  std::bind(&unit_attack::damage_calc_callback, this));
62 
63  find_widget<unit_preview_pane>("attacker_pane")
64  .set_display_data(*attacker_itor_);
65 
66  find_widget<unit_preview_pane>("defender_pane")
67  .set_display_data(*defender_itor_);
68 
69  selected_weapon_ = -1;
70 
71  listbox& weapon_list = find_widget<listbox>("weapon_list");
72  keyboard_capture(&weapon_list);
73 
74  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
75  static const config empty;
76  static const_attack_ptr no_weapon(new attack_type(empty));
77 
78  for(const auto & weapon : weapons_) {
79  const battle_context_unit_stats& attacker = weapon.get_attacker_stats();
80  const battle_context_unit_stats& defender = weapon.get_defender_stats();
81 
82  const attack_type& attacker_weapon =
83  *attacker.weapon;
84  const attack_type& defender_weapon = defender.weapon ?
85  *defender.weapon : *no_weapon;
86 
87  const color_t a_cth_color = game_config::red_to_green(attacker.chance_to_hit);
88  const color_t d_cth_color = game_config::red_to_green(defender.chance_to_hit);
89 
90  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
91  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
92 
93  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
94  if (!range.empty()) {
95  range = string_table["range_" + range];
96  }
97 
98  auto a_ctx = attacker_weapon.specials_context(
101  attacker_itor_->get_location(),
102  defender_itor_->get_location(), true, defender.weapon
103  );
104 
105  auto d_ctx = defender_weapon.specials_context(
108  defender_itor_->get_location(),
109  attacker_itor_->get_location(), false, attacker.weapon
110  );
111 
112  std::string types = attacker_weapon.effective_damage_type().first;
113  std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
114  if (!attw_type.empty()) {
115  attw_type = string_table["type_" + attw_type];
116  }
117  std::string def_types = defender_weapon.effective_damage_type().first;
118  std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
119  if (!defw_type.empty()) {
120  defw_type = string_table["type_" + defw_type];
121  }
122 
123  const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains", "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
124  std::string attw_specials = attacker_weapon.weapon_specials();
125  std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"});
126  std::string attw_specials_atk = attacker_weapon.weapon_specials_value({"attacks", "swarm"});
127  std::string attw_specials_cth = attacker_weapon.weapon_specials_value({"chance_to_hit"});
128  std::string attw_specials_others = attacker_weapon.weapon_specials_value(checking_tags_other);
129  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0 && defender_weapon.num_attacks() == 0 && defender.chance_to_hit == 0);
130  std::string defw_specials = defender_attack ? defender_weapon.weapon_specials() : "";
131  std::string defw_specials_dmg = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage"}) : "";
132  std::string defw_specials_atk = defender_attack ? defender_weapon.weapon_specials_value({"attacks", "swarm"}) : "";
133  std::string defw_specials_cth = defender_attack ? defender_weapon.weapon_specials_value({"chance_to_hit"}) : "";
134  std::string defw_specials_others = defender_attack ? defender_weapon.weapon_specials_value(checking_tags_other) : "";
135 
136  if(!attw_specials.empty()) {
137  attw_specials = " " + attw_specials;
138  }
139  if(!attw_specials_dmg.empty()) {
140  attw_specials_dmg = " " + attw_specials_dmg;
141  }
142  if(!attw_specials_atk.empty()) {
143  attw_specials_atk = " " + attw_specials_atk;
144  }
145  if(!attw_specials_cth.empty()) {
146  attw_specials_cth = " " + attw_specials_cth;
147  }
148  if(!attw_specials_others.empty()) {
149  attw_specials_others = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
150  }
151  if(!defw_specials.empty()) {
152  defw_specials = " " + defw_specials;
153  }
154  if(!defw_specials_dmg.empty()) {
155  defw_specials_dmg = " " + defw_specials_dmg;
156  }
157  if(!defw_specials_atk.empty()) {
158  defw_specials_atk = " " + defw_specials_atk;
159  }
160  if(!defw_specials_cth.empty()) {
161  defw_specials_cth = " " + defw_specials_cth;
162  }
163  if(!defw_specials_others.empty()) {
164  defw_specials_others = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
165  }
166 
167  std::stringstream attacker_stats, defender_stats, attacker_tooltip, defender_tooltip;
168 
169  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does not consider the swarm weapon special
170  attacker_stats << markup::bold(attw_name) << "\n"
171  << attw_type << "\n"
172  << attacker.damage << font::weapon_numbers_sep << attacker.num_blows
173  << attw_specials << "\n"
174  << markup::span_color(a_cth_color, attacker.chance_to_hit, "%");
175 
176  attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
177  << _("Type: ") << attw_type << "\n"
178  << _("Damage: ") << attacker.damage << markup::italic(attw_specials_dmg) << "\n"
179  << _("Attacks: ") << attacker.num_blows << markup::italic(attw_specials_atk) << "\n"
180  << _("Chance to hit: ") << markup::span_color(a_cth_color, attacker.chance_to_hit, "%")
181  << markup::italic(attw_specials_cth) << attw_specials_others;
182 
183  defender_stats << markup::bold(defw_name) << "\n"
184  << defw_type << "\n"
185  << defender.damage << font::weapon_numbers_sep << defender.num_blows
186  << defw_specials << "\n"
187  << markup::span_color(d_cth_color, defender.chance_to_hit, "%");
188 
189  defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
190  << _("Type: ") << defw_type << "\n"
191  << _("Damage: ") << defender.damage << markup::italic(defw_specials_dmg) << "\n"
192  << _("Attacks: ") << defender.num_blows << markup::italic(defw_specials_atk) << "\n"
193  << _("Chance to hit: ") << markup::span_color(d_cth_color, defender.chance_to_hit, "%")
194  << markup::italic(defw_specials_cth)
195  << defw_specials_others;
196 
198  widget_item item;
199 
200  item["use_markup"] = "true";
201 
202  item["label"] = attacker_weapon.icon();
203  data.emplace("attacker_weapon_icon", item);
204 
205  item["tooltip"] = attacker_tooltip.str();
206  item["label"] = attacker_stats.str();
207  data.emplace("attacker_weapon", item);
208  item["tooltip"] = "";
209 
210  item["label"] = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
211  data.emplace("range", item);
212 
213  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
214  item["label"] = defender_stats.str();
215  data.emplace("defender_weapon", item);
216 
217  item["tooltip"] = "";
218  item["label"] = defender_weapon.icon();
219  data.emplace("defender_weapon_icon", item);
220 
221  weapon_list.add_row(data);
222  }
223 
224  // If these two aren't the same size, we can't use list selection incides
225  // to access to weapons list!
226  assert(weapons_.size() == weapon_list.get_item_count());
227 
228  weapon_list.select_row(best_weapon_);
229 }
230 
232 {
233  if(get_retval() == retval::OK) {
234  selected_weapon_ = find_widget<listbox>("weapon_list").get_selected_row();
235  }
236 }
237 
238 } // namespace dialogs
std::string weapon_specials() const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:997
std::string weapon_specials_value(const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1045
const std::string & range() const
Definition: attack_type.hpp:46
const std::string & type() const
Definition: attack_type.hpp:44
int num_attacks() const
Definition: attack_type.hpp:53
const t_string & name() const
Definition: attack_type.hpp:42
specials_context_t specials_context(unit_const_ptr self, unit_const_ptr other, const map_location &unit_loc, const map_location &other_loc, bool attacking, const_attack_ptr other_attack) const
const std::string & icon() const
Definition: attack_type.hpp:45
int damage() const
Definition: attack_type.hpp:52
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Definition: abilities.cpp:1323
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:158
Abstract base class for all modal dialogs.
virtual void post_show() override
Actions to be taken after the window has been shown.
int selected_weapon_
The index of the selected weapon.
Definition: unit_attack.hpp:50
unit_map::iterator defender_itor_
Iterator pointing to the defender.
Definition: unit_attack.hpp:56
int best_weapon_
The best weapon, aka the one high-lighted.
Definition: unit_attack.hpp:62
virtual void pre_show() override
Actions to be taken before showing the window.
Definition: unit_attack.cpp:57
unit_map::iterator attacker_itor_
Iterator pointing to the attacker.
Definition: unit_attack.hpp:53
std::vector< battle_context > weapons_
List of all battle contexts used for getting the weapons.
Definition: unit_attack.hpp:59
The listbox class.
Definition: listbox.hpp:41
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:92
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:280
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:159
void keyboard_capture(widget *widget)
Definition: window.cpp:1193
int get_retval()
Definition: window.hpp:402
bool empty() const
Definition: tstring.hpp:195
Container associating units to locations.
Definition: map.hpp:98
static std::string _(const char *str)
Definition: gettext.hpp:93
This file contains the window object, this object is a top level container which has the event manage...
symbol_table string_table
Definition: language.cpp:64
const std::string unicode_em_dash
Definition: constants.cpp:44
const std::string weapon_numbers_sep
Definition: constants.cpp:49
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....
REGISTER_DIALOG(editor_edit_unit)
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:153
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition: markup.hpp:138
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:87
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const const_attack_ptr &secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds, bool attacking)
Make the unit on tile 'a' attack the unit on tile 'b'.
Definition: udisplay.cpp:595
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::string_view data
Definition: picture.cpp:178
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
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
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:217