The Battle for Wesnoth  1.19.0-dev
unit_attack.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 - 2024
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 "font/text_formatting.hpp"
22 #include "gui/widgets/button.hpp"
23 #include "gui/widgets/label.hpp"
24 #include "gui/widgets/image.hpp"
25 #include "gui/widgets/listbox.hpp"
26 #include "gui/widgets/settings.hpp"
28 #include "gui/widgets/window.hpp"
29 #include "game_config.hpp"
30 #include "game_display.hpp"
31 #include "gettext.hpp"
32 #include "help/help.hpp"
33 #include "language.hpp"
34 #include "color.hpp"
35 #include "units/unit.hpp"
36 
37 #include <functional>
38 
39 namespace gui2::dialogs
40 {
41 
43 
44 unit_attack::unit_attack(const unit_map::iterator& attacker_itor,
45  const unit_map::iterator& defender_itor,
46  std::vector<battle_context>&& weapons,
47  const int best_weapon)
48  : modal_dialog(window_id())
49  , selected_weapon_(-1)
50  , attacker_itor_(attacker_itor)
51  , defender_itor_(defender_itor)
52  , weapons_(std::move(weapons))
53  , best_weapon_(best_weapon)
54 {
55 }
56 
58 {
59  const std::size_t index = find_widget<listbox>(get_window(), "weapon_list", false).get_selected_row();
60  attack_predictions::display(weapons_[index], attacker_itor_.get_shared_ptr(), defender_itor_.get_shared_ptr());
61 }
62 
64 {
66  find_widget<button>(&window, "damage_calculation", false),
67  std::bind(&unit_attack::damage_calc_callback, this));
68 
69  find_widget<unit_preview_pane>(&window, "attacker_pane", false)
70  .set_displayed_unit(*attacker_itor_);
71 
72  find_widget<unit_preview_pane>(&window, "defender_pane", false)
73  .set_displayed_unit(*defender_itor_);
74 
75  selected_weapon_ = -1;
76 
77  listbox& weapon_list = find_widget<listbox>(&window, "weapon_list", false);
78  window.keyboard_capture(&weapon_list);
79 
80  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
81  static const config empty;
82  static const_attack_ptr no_weapon(new attack_type(empty));
83 
84  for(const auto & weapon : weapons_) {
85  const battle_context_unit_stats& attacker = weapon.get_attacker_stats();
86  const battle_context_unit_stats& defender = weapon.get_defender_stats();
87 
88  const attack_type& attacker_weapon =
89  *attacker.weapon;
90  const attack_type& defender_weapon = defender.weapon ?
91  *defender.weapon : *no_weapon;
92 
93  const color_t a_cth_color = game_config::red_to_green(attacker.chance_to_hit);
94  const color_t d_cth_color = game_config::red_to_green(defender.chance_to_hit);
95 
96  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
97  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
98 
99  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
100  if (!range.empty()) {
101  range = string_table["range_" + range];
102  }
103 
104  auto a_ctx = attacker_weapon.specials_context(
107  attacker_itor_->get_location(),
108  defender_itor_->get_location(), true, defender.weapon
109  );
110 
111  auto d_ctx = defender_weapon.specials_context(
114  defender_itor_->get_location(),
115  attacker_itor_->get_location(), false, attacker.weapon
116  );
117 
118  std::pair<std::string, std::string> types = attacker_weapon.damage_type();
119  std::string attw_type_second = types.second;
120  std::string attw_type = !(types.first).empty() ? types.first : attacker_weapon.type();
121  if (!attw_type.empty()) {
122  attw_type = string_table["type_" + attw_type];
123  }
124  if (!attw_type_second.empty()) {
125  attw_type_second = ", " + string_table["type_" + attw_type_second];
126  }
127  std::pair<std::string, std::string> def_types = defender_weapon.damage_type();
128  std::string defw_type_second = def_types.second;
129  std::string defw_type = !(def_types.first).empty() ? def_types.first : defender_weapon.type();
130  if (!defw_type.empty()) {
131  defw_type = string_table["type_" + defw_type];
132  }
133  if (!defw_type_second.empty()) {
134  defw_type_second = ", " + string_table["type_" + defw_type_second];
135  }
136 
137  const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains", "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
138  std::string attw_specials = attacker_weapon.weapon_specials();
139  std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"});
140  std::string attw_specials_atk = attacker_weapon.weapon_specials_value({"attacks", "swarm"});
141  std::string attw_specials_cth = attacker_weapon.weapon_specials_value({"chance_to_hit"});
142  std::string attw_specials_others = attacker_weapon.weapon_specials_value(checking_tags_other);
143  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0 && defender_weapon.num_attacks() == 0 && defender.chance_to_hit == 0);
144  std::string defw_specials = defender_attack ? defender_weapon.weapon_specials() : "";
145  std::string defw_specials_dmg = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage"}) : "";
146  std::string defw_specials_atk = defender_attack ? defender_weapon.weapon_specials_value({"attacks", "swarm"}) : "";
147  std::string defw_specials_cth = defender_attack ? defender_weapon.weapon_specials_value({"chance_to_hit"}) : "";
148  std::string defw_specials_others = defender_attack ? defender_weapon.weapon_specials_value(checking_tags_other) : "";
149 
150  if(!attw_specials.empty()) {
151  attw_specials = " " + attw_specials;
152  }
153  if(!attw_specials_dmg.empty()) {
154  attw_specials_dmg = " " + attw_specials_dmg;
155  }
156  if(!attw_specials_atk.empty()) {
157  attw_specials_atk = " " + attw_specials_atk;
158  }
159  if(!attw_specials_cth.empty()) {
160  attw_specials_cth = " " + attw_specials_cth;
161  }
162  if(!attw_specials_others.empty()) {
163  attw_specials_others = "\n" + ("<b>" + _("Other aspects: ") + "</b>") + "\n" + ("<i>"+attw_specials_others+"</i>");
164  }
165  if(!defw_specials.empty()) {
166  defw_specials = " " + defw_specials;
167  }
168  if(!defw_specials_dmg.empty()) {
169  defw_specials_dmg = " " + defw_specials_dmg;
170  }
171  if(!defw_specials_atk.empty()) {
172  defw_specials_atk = " " + defw_specials_atk;
173  }
174  if(!defw_specials_cth.empty()) {
175  defw_specials_cth = " " + defw_specials_cth;
176  }
177  if(!defw_specials_others.empty()) {
178  defw_specials_others = "\n" + ("<b>" + _("Other aspects: ") + "</b>") + "\n" + ("<i>"+defw_specials_others+"</i>");
179  }
180 
181  std::stringstream attacker_stats, defender_stats, attacker_tooltip, defender_tooltip;
182 
183  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does not consider the swarm weapon special
184  attacker_stats << "<b>" << attw_name << "</b>" << "\n"
185  << attw_type << attw_type_second << "\n"
186  << attacker.damage << font::weapon_numbers_sep << attacker.num_blows
187  << attw_specials << "\n"
188  << font::span_color(a_cth_color) << attacker.chance_to_hit << "%</span>";
189 
190  attacker_tooltip << _("Weapon: ") << "<b>" << attw_name << "</b>" << "\n"
191  << _("Type: ") << attw_type << attw_type_second << "\n"
192  << _("Damage: ") << attacker.damage << "<i>" << attw_specials_dmg << "</i>" << "\n"
193  << _("Attacks: ") << attacker.num_blows << "<i>" << attw_specials_atk << "</i>" << "\n"
194  << _("Chance to hit: ") << font::span_color(a_cth_color) << attacker.chance_to_hit << "%</span>"<< "<i>" << attw_specials_cth << "</i>"
195  << attw_specials_others;
196 
197  defender_stats << "<b>" << defw_name << "</b>" << "\n"
198  << defw_type << defw_type_second << "\n"
199  << defender.damage << font::weapon_numbers_sep << defender.num_blows
200  << defw_specials << "\n"
201  << font::span_color(d_cth_color) << defender.chance_to_hit << "%</span>";
202 
203  defender_tooltip << _("Weapon: ") << "<b>" << defw_name << "</b>" << "\n"
204  << _("Type: ") << defw_type << defw_type_second << "\n"
205  << _("Damage: ") << defender.damage << "<i>" << defw_specials_dmg << "</i>" << "\n"
206  << _("Attacks: ") << defender.num_blows << "<i>" << defw_specials_atk << "</i>" << "\n"
207  << _("Chance to hit: ") << font::span_color(d_cth_color) << defender.chance_to_hit << "%</span>"<< "<i>" << defw_specials_cth << "</i>"
208  << defw_specials_others;
209 
212 
213  item["use_markup"] = "true";
214 
215  item["label"] = attacker_weapon.icon();
216  data.emplace("attacker_weapon_icon", item);
217 
218  item["tooltip"] = attacker_tooltip.str();
219  item["label"] = attacker_stats.str();
220  data.emplace("attacker_weapon", item);
221  item["tooltip"] = "";
222 
223  item["label"] = "<span color='#a69275'>" + font::unicode_em_dash + " " + range + " " + font::unicode_em_dash + "</span>";
224  data.emplace("range", item);
225 
226  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
227  item["label"] = defender_stats.str();
228  data.emplace("defender_weapon", item);
229 
230  item["tooltip"] = "";
231  item["label"] = defender_weapon.icon();
232  data.emplace("defender_weapon_icon", item);
233 
234  weapon_list.add_row(data);
235  }
236 
237  // If these two aren't the same size, we can't use list selection incides
238  // to access to weapons list!
239  assert(weapons_.size() == weapon_list.get_item_count());
240 
241  weapon_list.select_row(best_weapon_);
242 }
243 
245 {
246  if(get_retval() == retval::OK) {
247  selected_weapon_ = find_widget<listbox>(&window, "weapon_list", false).get_selected_row();
248  }
249 }
250 
251 } // namespace dialogs
std::string weapon_specials() const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:913
const std::string & range() const
Definition: attack_type.hpp:47
const std::string & type() const
Definition: attack_type.hpp:45
std::string weapon_specials_value(const std::set< std::string > checking_tags) const
Definition: abilities.cpp:956
int num_attacks() const
Definition: attack_type.hpp:54
const t_string & name() const
Definition: attack_type.hpp:43
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:46
int damage() const
Definition: attack_type.hpp:53
std::pair< std::string, std::string > damage_type() const
return a modified damage type and/or add a secondary_type for hybrid use if special is active.
Definition: abilities.cpp:1200
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:167
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
Abstract base class for all modal dialogs.
int get_retval() const
Returns the cached window exit code.
window * get_window()
Returns a pointer to the dialog's window.
This shows the dialog for attacking units.
Definition: unit_attack.hpp:42
virtual void pre_show(window &window) override
Actions to be taken before showing the window.
Definition: unit_attack.cpp:63
int selected_weapon_
The index of the selected weapon.
Definition: unit_attack.hpp:66
unit_map::iterator defender_itor_
Iterator pointing to the defender.
Definition: unit_attack.hpp:72
int best_weapon_
The best weapon, aka the one high-lighted.
Definition: unit_attack.hpp:78
unit_map::iterator attacker_itor_
Iterator pointing to the attacker.
Definition: unit_attack.hpp:69
virtual void post_show(window &window) override
Actions to be taken after the window has been shown.
std::vector< battle_context > weapons_
List of all battle contexts used for getting the weapons.
Definition: unit_attack.hpp:75
The listbox class.
Definition: listbox.hpp:43
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:59
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:243
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:124
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:67
void keyboard_capture(widget *widget)
Definition: window.cpp:1225
bool empty() const
Definition: tstring.hpp:187
Container associating units to locations.
Definition: map.hpp:99
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:65
const std::string unicode_em_dash
Definition: constants.cpp:44
std::string span_color(const color_t &color)
Returns a Pango formatting string using the provided color_t object.
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(tod_new_schedule)
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:179
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:35
std::map< std::string, t_string > widget_item
Definition: widget.hpp:32
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, 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:599
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::string_view data
Definition: picture.cpp:199
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
This file contains the settings handling of the widget library.
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:218