The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
attack_predictions.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
18 
19 #include "attack_prediction.hpp"
20 #include "color.hpp"
21 #include "config.hpp"
22 #include "font/text_formatting.hpp"
23 #include "formatter.hpp"
24 #include "formula/variant.hpp"
25 #include "game_board.hpp"
26 #include "game_config.hpp"
28 #include "gui/widgets/drawing.hpp"
29 #include "gui/widgets/label.hpp"
30 #include "gui/widgets/settings.hpp"
31 #include "gui/widgets/window.hpp"
32 #include "gettext.hpp"
33 #include "language.hpp"
34 #include "resources.hpp"
35 #include "units/abilities.hpp"
36 #include "units/unit.hpp"
37 
38 #include <iomanip>
39 
40 namespace gui2
41 {
42 namespace dialogs
43 {
44 
45 REGISTER_DIALOG(attack_predictions)
46 
47 const unsigned int attack_predictions::graph_width = 270;
48 const unsigned int attack_predictions::graph_height = 170;
49 const unsigned int attack_predictions::graph_max_rows = 10;
50 
51 attack_predictions::attack_predictions(battle_context& bc, const unit& attacker, const unit& defender)
52  : attacker_data_(attacker, bc.get_attacker_combatant(), bc.get_attacker_stats())
53  , defender_data_(defender, bc.get_defender_combatant(), bc.get_defender_stats())
54 {
55 }
56 
58 {
61 }
62 
63 static std::string get_probability_string(const double prob)
64 {
65  std::ostringstream ss;
66 
67  if(prob > 0.9995) {
68  ss << "100%";
69  } else {
70  ss << std::fixed << std::setprecision(1) << 100.0 * prob << '%';
71  }
72 
73  return ss.str();
74 }
75 
77 {
78  // Each data widget in this dialog has its id prefixed by either of these identifiers.
79  const std::string widget_id_prefix = attacker.stats_.is_attacker ? "attacker" : "defender";
80 
81  const auto get_prefixed_widget_id = [&widget_id_prefix](const std::string& id) {
82  return (formatter() << widget_id_prefix << "_" << id).str();
83  };
84 
85  // Helpers for setting or hiding labels
86  const auto set_label_helper = [&](const std::string& id, const std::string& value) {
87  find_widget<label>(&window, get_prefixed_widget_id(id), false).set_label(value);
88  };
89 
90  const auto hide_label_helper = [&](const std::string& id) {
91  find_widget<label>(&window, get_prefixed_widget_id(id), false).set_visible(widget::visibility::invisible);
92  find_widget<label>(&window, get_prefixed_widget_id(id) + "_label" , false).set_visible(widget::visibility::invisible);
93  };
94 
95  std::stringstream ss;
96 
97  //
98  // Always visible fields
99  //
100 
101  // Unscathed probability
102  const color_t ndc_color = game_config::red_to_green(attacker.combatant_.untouched * 100);
103 
104  ss << font::span_color(ndc_color) << get_probability_string(attacker.combatant_.untouched) << "</span>";
105  set_label_helper("chance_unscathed", ss.str());
106 
107  // HP probability graph
108  drawing& graph_widget = find_widget<drawing>(&window, get_prefixed_widget_id("hp_graph"), false);
109  draw_hp_graph(graph_widget, attacker, defender);
110 
111  //
112  // Weapon detail fields (only shown if a weapon is present)
113  //
114 
115  if(!attacker.stats_.weapon) {
116  set_label_helper("base_damage", _("No usable weapon"));
117 
118  // FIXME: would rather have a list somewhere that I can loop over instead of hardcoding...
119  hide_label_helper("tod_modifier");
120  hide_label_helper("leadership_modifier");
121  hide_label_helper("slowed_modifier");
122 
123  return;
124  }
125 
126  ss.str("");
127 
128  // Set specials context (for safety, it should not have changed normally).
129  const_attack_ptr weapon = attacker.stats_.weapon;
130  weapon->set_specials_context(attacker.unit_.get_location(), defender.unit_.get_location(), attacker.stats_.is_attacker, defender.stats_.weapon);
131 
132  // Get damage modifiers.
133  unit_ability_list dmg_specials = weapon->get_specials("damage");
134  unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), attacker.stats_.backstab_pos);
135 
136  // Get the SET damage modifier, if any.
137  auto set_dmg_effect = std::find_if(dmg_effect.begin(), dmg_effect.end(),
138  [](const unit_abilities::individual_effect& e) { return e.type == unit_abilities::SET; }
139  );
140 
141  // Either user the SET modifier or the base weapon damage.
142  if(set_dmg_effect == dmg_effect.end()) {
143  ss << weapon->damage() << " (<i>" << weapon->name() << "</i>)";
144  } else {
145  assert(set_dmg_effect->ability);
146  ss << set_dmg_effect->value << " (<i>" << (*set_dmg_effect->ability)["name"] << "</i>)";
147  }
148 
149  // Process the ADD damage modifiers.
150  for(const auto& e : dmg_effect) {
151  if(e.type == unit_abilities::ADD) {
152  ss << "\n";
153 
154  if(e.value >= 0) {
155  ss << '+';
156  }
157 
158  ss << e.value;
159  ss << " (<i>" << (*e.ability)["name"] << "</i>)";
160  }
161  }
162 
163  // Process the MUL damage modifiers.
164  for(const auto& e : dmg_effect) {
165  if(e.type == unit_abilities::MUL) {
166  ss << "\n";
167  ss << font::unicode_multiplication_sign << (e.value / 100);
168 
169  if(e.value % 100) {
170  ss << "." << ((e.value % 100) / 10);
171  if(e.value % 10) {
172  ss << (e.value % 10);
173  }
174  }
175 
176  ss << " (<i>" << (*e.ability)["name"] << "</i>)";
177  }
178  }
179 
180  set_label_helper("base_damage", ss.str());
181 
182  ss.str("");
183 
184  // Resistance modifier.
185  const int resistance_modifier = defender.unit_.damage_from(*weapon, !attacker.stats_.is_attacker, defender.unit_.get_location());
186  if(resistance_modifier != 100) {
187  if(attacker.stats_.is_attacker) {
188  ss << _("Defender resistance vs") << " ";
189  } else {
190  ss << _("Attacker vulnerability vs") << " ";
191  }
192 
193  ss << string_table["type_" + weapon->type()];
194 
195  set_label_helper("resis_label", ss.str());
196 
197  ss.str("");
198  ss << font::unicode_multiplication_sign << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
199 
200  set_label_helper("resis", ss.str());
201  }
202 
203  ss.str("");
204 
205  // TODO: color format the modifiers
206 
207  // Time of day modifier.
208  const unit& u = attacker.unit_;
209 
210  const int tod_modifier = combat_modifier(resources::gameboard->units(), resources::gameboard->map(),
211  u.get_location(), u.alignment(), u.is_fearless());
212 
213  if(tod_modifier != 0) {
214  set_label_helper("tod_modifier", utils::signed_percent(tod_modifier));
215  } else {
216  hide_label_helper("tod_modifier");
217  }
218 
219  // Leadership bonus.
220  const int leadership_bonus = under_leadership(resources::gameboard->units(), attacker.unit_.get_location()).first;
221 
222  if(leadership_bonus != 0) {
223  set_label_helper("leadership_modifier", utils::signed_percent(leadership_bonus));
224  } else {
225  hide_label_helper("leadership_modifier");
226  }
227 
228  // Slowed penalty.
229  if(attacker.stats_.is_slowed) {
230  set_label_helper("slowed_modifier", "/ 2");
231  } else {
232  hide_label_helper("slowed_modifier");
233  }
234 
235  // Total damage.
236  const int base_damage = weapon->damage();
237 
238  color_t dmg_color = font::weapon_color;
239  if(attacker.stats_.damage > base_damage) {
240  dmg_color = font::good_dmg_color;
241  } else if(attacker.stats_.damage < base_damage) {
242  dmg_color = font::bad_dmg_color;
243  }
244 
245  ss << font::span_color(dmg_color) << attacker.stats_.damage << "</span>"
247 
248  set_label_helper("total_damage", ss.str());
249 
250  // Chance to hit
251  const color_t cth_color = game_config::red_to_green(attacker.stats_.chance_to_hit);
252 
253  ss.str("");
254  ss << font::span_color(cth_color) << attacker.stats_.chance_to_hit << "%</span>";
255 
256  set_label_helper("chance_to_hit", ss.str());
257 }
258 
259 void attack_predictions::draw_hp_graph(drawing& hp_graph, const combatant_data& attacker, const combatant_data& defender)
260 {
261  // Font size. If you change this, you must update the separator space.
262  // TODO: probably should remove this.
263  const int fs = font::SIZE_SMALL;
264 
265  // Space before HP separator.
266  const int hp_sep = 30;
267 
268  // Space after percentage separator.
269  const int percent_sep = 50;
270 
271  // Bar space between both separators.
272  const int bar_space = graph_width - hp_sep - percent_sep - 4;
273 
274  // Set some variables for the WML portion of the graph to use.
275  canvas& hp_graph_canvas = hp_graph.get_drawing_canvas();
276 
277  hp_graph_canvas.set_variable("hp_column_width", wfl::variant(hp_sep));
278  hp_graph_canvas.set_variable("chance_column_width", wfl::variant(percent_sep));
279 
280  config cfg, shape;
281 
282  int i = 0;
283 
284  // Draw the rows (lower HP values are at the bottom).
285  for(const auto& probability : get_hitpoint_probabilities(attacker.combatant_.hp_dist)) {
286 
287  // Get the HP and probability.
288  int hp; double prob;
289  std::tie(hp, prob) = probability;
290 
291  color_t row_color;
292 
293  // Death line is red.
294  if(hp == 0) {
295  row_color = {229, 0, 0};
296  }
297 
298  // Below current hitpoints value is orange.
299  else if(hp < static_cast<int>(attacker.stats_.hp)) {
300  // Stone is grey.
301  if(defender.stats_.petrifies) {
302  row_color = {154, 154, 154};
303  } else {
304  row_color = {244, 201, 0};
305  }
306  }
307 
308  // Current hitpoints value and above is green.
309  else {
310  row_color = {8, 202, 0};
311  }
312 
313  shape["text"] = hp;
314  shape["x"] = 4;
315  shape["y"] = 2 + (fs + 2) * i;
316  shape["w"] = "(text_width)";
317  shape["h"] = "(text_height)";
318  shape["font_size"] = 12;
319  shape["color"] = "255, 255, 255, 255";
320  shape["text_alignment"] = "(text_alignment)";
321 
322  cfg.add_child("text", shape);
323 
324  shape.clear();
325  shape["text"] = get_probability_string(prob);
326  shape["x"] = graph_width - percent_sep + 2;
327  shape["y"] = 2 + (fs + 2) * i;
328  shape["w"] = "(text_width)";
329  shape["h"] = "(text_height)";
330  shape["font_size"] = 12;
331  shape["color"] = "255, 255, 255, 255";
332  shape["text_alignment"] = "(text_alignment)";
333 
334  cfg.add_child("text", shape);
335 
336  const int bar_len = std::max(static_cast<int>((prob * (bar_space - 4)) + 0.5), 2);
337 
338  const SDL_Rect bar_rect_1 {
339  hp_sep + 4,
340  6 + (fs + 2) * i,
341  bar_len,
342  8
343  };
344 
345  shape.clear();
346  shape["x"] = bar_rect_1.x;
347  shape["y"] = bar_rect_1.y;
348  shape["w"] = bar_rect_1.w;
349  shape["h"] = bar_rect_1.h;
350  shape["fill_color"] = row_color.to_rgba_string();
351 
352  cfg.add_child("rectangle", shape);
353 
354  ++i;
355  }
356 
357  hp_graph.append_drawing_data(cfg);
358 }
359 
361 {
362  hp_probability_vector res, temp_vec;
363 
364  // First, extract any relevant probability values
365  for(int i = 0; i < static_cast<int>(hp_dist.size()); ++i) {
366  const double prob = hp_dist[i];
367 
368  // We keep only values above 0.1%.
369  if(prob > 0.001) {
370  temp_vec.emplace_back(i, prob);
371  }
372  }
373 
374  // Then sort by descending probability.
375  std::sort(temp_vec.begin(), temp_vec.end(), [](const hp_probability_t& pair1, const hp_probability_t& pair2) {
376  return pair1.second > pair2.second;
377  });
378 
379  // Take only the highest probability values.;
380  std::copy_n(temp_vec.begin(), std::min<int>(graph_max_rows, temp_vec.size()), std::back_inserter(res));
381 
382  // Then, we sort the hitpoint values in descending order.
383  std::sort(res.begin(), res.end(), [](const hp_probability_t& pair1, const hp_probability_t& pair2) {
384  return pair1.first > pair2.first;
385  });
386 
387  return res;
388 }
389 
390 } // namespace dialogs
391 } // namespace gui2
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition: attack.hpp:49
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
std::vector< char_t > string
std::vector< hp_probability_t > hp_probability_vector
This class represents a single unit of a specific type.
Definition: unit.hpp:100
static std::string get_probability_string(const double prob)
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
Definition: attack.hpp:70
void set_variable(const std::string &key, const wfl::variant &value)
Definition: canvas.hpp:171
This file contains the window object, this object is a top level container which has the event manage...
const color_t good_dmg_color
A widget to draw upon.
Definition: drawing.hpp:38
static const unsigned int graph_max_rows
bool is_slowed
True if the unit is slowed at the beginning of the battle.
Definition: attack.hpp:53
void clear()
Definition: config.cpp:807
Definitions for the interface to Wesnoth Markup Language (WML).
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:72
std::string to_rgba_string() const
Returns the stored color as an "R,G,B,A" string.
Definition: color.cpp:110
Generic file dialog.
Definition: field-fwd.hpp:22
const color_t weapon_color
std::pair< int, double > hp_probability_t
void append_drawing_data(const ::config &cfg)
Definition: drawing.hpp:53
bool backstab_pos
True if the attacker is in position to backstab the defender (this is used to determine whether to ap...
Definition: attack.hpp:59
std::string span_color(const color_t &color)
Returns a Pango formatting string using the provided color_t object.
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
const color_t bad_dmg_color
int damage
Effective damage of the weapon (all factors accounted for).
Definition: attack.hpp:73
This file contains the settings handling of the widget library.
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1141
std::ostringstream wrapper.
Definition: formatter.hpp:38
void set_data(window &window, const combatant_data &attacker, const combatant_data &defender)
canvas & get_drawing_canvas()
Definition: drawing.hpp:43
game_board * gameboard
Definition: resources.cpp:20
const std::string unicode_multiplication_sign
Definition: constants.cpp:41
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:170
Various uncategorised dialogs.
A simple canvas which can be drawn upon.
Definition: canvas.hpp:41
virtual void pre_show(window &window) override
Inherited from modal_dialog.
The user set the widget invisible, that means:
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
size_t i
Definition: function.cpp:933
void draw_hp_graph(drawing &hp_graph, const combatant_data &attacker, const combatant_data &defender)
static int sort(lua_State *L)
Definition: ltablib.cpp:411
config & add_child(config_key_type key)
Definition: config.cpp:473
const std::string weapon_numbers_sep
Definition: constants.cpp:44
color_t red_to_green(int val, bool for_text)
Return a color corresponding to the value val red for val=0 to green for val=100, passing by yellow...
symbol_table string_table
Definition: language.cpp:62
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1038
unit_type::ALIGNMENT alignment() const
The alignment of this unit.
Definition: unit.hpp:370
static const unsigned int graph_width
hp_probability_vector get_hitpoint_probabilities(const std::vector< double > &hp_dist)
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition: attack.hpp:77
std::pair< int, map_location > under_leadership(const unit_map &units, const map_location &loc)
Tests if the unit at loc is currently affected by leadership.
Definition: attack.cpp:1576
bool is_attacker
True if the unit is the attacker.
Definition: attack.hpp:51
#define e
static const unsigned int graph_height
int damage_from(const attack_type &attack, bool attacker, const map_location &loc) const
Calculates the damage this unit would take from a certain attack.
Definition: unit.hpp:828
bool petrifies
Attack petrifies opponent when it hits.
Definition: attack.hpp:56
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
attack_predictions(battle_context &bc, const unit &attacker, const unit &defender)
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:37
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:62
const int SIZE_SMALL
Definition: constants.cpp:23
int combat_modifier(const unit_map &units, const gamemap &map, const map_location &loc, unit_type::ALIGNMENT 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:1587