The Battle for Wesnoth  1.15.0-dev
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 https://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/window.hpp"
31 #include "gettext.hpp"
32 #include "language.hpp"
33 #include "resources.hpp"
34 #include "units/abilities.hpp"
35 #include "units/unit.hpp"
36 
37 #include <iomanip>
38 
39 namespace gui2
40 {
41 namespace dialogs
42 {
43 
44 REGISTER_DIALOG(attack_predictions)
45 
46 const unsigned int attack_predictions::graph_width = 270;
47 const unsigned int attack_predictions::graph_height = 170;
48 const unsigned int attack_predictions::graph_max_rows = 10;
49 
50 attack_predictions::attack_predictions(battle_context& bc, const unit& attacker, const unit& defender)
51  : attacker_data_(attacker, bc.get_attacker_combatant(), bc.get_attacker_stats())
52  , defender_data_(defender, bc.get_defender_combatant(), bc.get_defender_stats())
53 {
54 }
55 
57 {
60 }
61 
62 static std::string get_probability_string(const double prob)
63 {
64  std::ostringstream ss;
65 
66  if(prob > 0.9995) {
67  ss << "100%";
68  } else {
69  ss << std::fixed << std::setprecision(1) << 100.0 * prob << '%';
70  }
71 
72  return ss.str();
73 }
74 
76 {
77  // Each data widget in this dialog has its id prefixed by either of these identifiers.
78  const std::string widget_id_prefix = attacker.stats_.is_attacker ? "attacker" : "defender";
79 
80  const auto get_prefixed_widget_id = [&widget_id_prefix](const std::string& id) {
81  return (formatter() << widget_id_prefix << "_" << id).str();
82  };
83 
84  // Helpers for setting or hiding labels
85  const auto set_label_helper = [&](const std::string& id, const std::string& value) {
86  find_widget<label>(&window, get_prefixed_widget_id(id), false).set_label(value);
87  };
88 
89  const auto hide_label_helper = [&](const std::string& id) {
90  find_widget<label>(&window, get_prefixed_widget_id(id), false).set_visible(widget::visibility::invisible);
91  find_widget<label>(&window, get_prefixed_widget_id(id) + "_label" , false).set_visible(widget::visibility::invisible);
92  };
93 
94  std::stringstream ss;
95 
96  //
97  // Always visible fields
98  //
99 
100  // Unscathed probability
101  const color_t ndc_color = game_config::red_to_green(attacker.combatant_.untouched * 100);
102 
103  ss << font::span_color(ndc_color) << get_probability_string(attacker.combatant_.untouched) << "</span>";
104  set_label_helper("chance_unscathed", ss.str());
105 
106  // HP probability graph
107  drawing& graph_widget = find_widget<drawing>(&window, get_prefixed_widget_id("hp_graph"), false);
108  draw_hp_graph(graph_widget, attacker, defender);
109 
110  //
111  // Weapon detail fields (only shown if a weapon is present)
112  //
113 
114  if(!attacker.stats_.weapon) {
115  set_label_helper("base_damage", _("No usable weapon"));
116 
117  // FIXME: would rather have a list somewhere that I can loop over instead of hardcoding...
118  hide_label_helper("tod_modifier");
119  hide_label_helper("leadership_modifier");
120  hide_label_helper("slowed_modifier");
121 
122  return;
123  }
124 
125  ss.str("");
126 
127  // Set specials context (for safety, it should not have changed normally).
128  const_attack_ptr weapon = attacker.stats_.weapon, opp_weapon = defender.stats_.weapon;
129  auto ctx = weapon->specials_context(&attacker.unit_, &defender.unit_, attacker.unit_.get_location(), defender.unit_.get_location(), attacker.stats_.is_attacker, opp_weapon);
130  boost::optional<decltype(ctx)> opp_ctx;
131 
132  if(opp_weapon) {
133  opp_ctx.emplace(opp_weapon->specials_context(&defender.unit_, &attacker.unit_, defender.unit_.get_location(), attacker.unit_.get_location(), defender.stats_.is_attacker, weapon));
134  }
135 
136  // Get damage modifiers.
137  unit_ability_list dmg_specials = weapon->get_specials("damage");
138  unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), attacker.stats_.backstab_pos);
139 
140  // Get the SET damage modifier, if any.
141  auto set_dmg_effect = std::find_if(dmg_effect.begin(), dmg_effect.end(),
142  [](const unit_abilities::individual_effect& e) { return e.type == unit_abilities::SET; }
143  );
144 
145  // Either user the SET modifier or the base weapon damage.
146  if(set_dmg_effect == dmg_effect.end()) {
147  ss << weapon->damage() << " (<i>" << weapon->name() << "</i>)";
148  } else {
149  assert(set_dmg_effect->ability);
150  ss << set_dmg_effect->value << " (<i>" << (*set_dmg_effect->ability)["name"] << "</i>)";
151  }
152 
153  // Process the ADD damage modifiers.
154  for(const auto& e : dmg_effect) {
155  if(e.type == unit_abilities::ADD) {
156  ss << "\n";
157 
158  if(e.value >= 0) {
159  ss << '+';
160  }
161 
162  ss << e.value;
163  ss << " (<i>" << (*e.ability)["name"] << "</i>)";
164  }
165  }
166 
167  // Process the MUL damage modifiers.
168  for(const auto& e : dmg_effect) {
169  if(e.type == unit_abilities::MUL) {
170  ss << "\n";
171  ss << font::unicode_multiplication_sign << (e.value / 100);
172 
173  if(e.value % 100) {
174  ss << "." << ((e.value % 100) / 10);
175  if(e.value % 10) {
176  ss << (e.value % 10);
177  }
178  }
179 
180  ss << " (<i>" << (*e.ability)["name"] << "</i>)";
181  }
182  }
183 
184  set_label_helper("base_damage", ss.str());
185 
186  ss.str("");
187 
188  // Resistance modifier.
189  const int resistance_modifier = defender.unit_.damage_from(*weapon, !attacker.stats_.is_attacker, defender.unit_.get_location(), opp_weapon);
190  if(resistance_modifier != 100) {
191  if(attacker.stats_.is_attacker) {
192  ss << _("Defender resistance vs") << " ";
193  } else {
194  ss << _("Attacker vulnerability vs") << " ";
195  }
196 
197  ss << string_table["type_" + weapon->type()];
198 
199  set_label_helper("resis_label", ss.str());
200 
201  ss.str("");
202  ss << font::unicode_multiplication_sign << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
203 
204  set_label_helper("resis", ss.str());
205  }
206 
207  ss.str("");
208 
209  // TODO: color format the modifiers
210 
211  // Time of day modifier.
212  const unit& u = attacker.unit_;
213 
214  const int tod_modifier = combat_modifier(resources::gameboard->units(), resources::gameboard->map(),
215  u.get_location(), u.alignment(), u.is_fearless());
216 
217  if(tod_modifier != 0) {
218  set_label_helper("tod_modifier", utils::signed_percent(tod_modifier));
219  } else {
220  hide_label_helper("tod_modifier");
221  }
222 
223  // Leadership bonus.
224  const int leadership_bonus = under_leadership(resources::gameboard->units(), attacker.unit_.get_location(), weapon, opp_weapon).first;
225 
226  if(leadership_bonus != 0) {
227  set_label_helper("leadership_modifier", utils::signed_percent(leadership_bonus));
228  } else {
229  hide_label_helper("leadership_modifier");
230  }
231 
232  // Slowed penalty.
233  if(attacker.stats_.is_slowed) {
234  set_label_helper("slowed_modifier", "/ 2");
235  } else {
236  hide_label_helper("slowed_modifier");
237  }
238 
239  // Total damage.
240  const int base_damage = weapon->damage();
241 
242  color_t dmg_color = font::weapon_color;
243  if(attacker.stats_.damage > base_damage) {
244  dmg_color = font::good_dmg_color;
245  } else if(attacker.stats_.damage < base_damage) {
246  dmg_color = font::bad_dmg_color;
247  }
248 
249  ss << font::span_color(dmg_color) << attacker.stats_.damage << "</span>"
251 
252  set_label_helper("total_damage", ss.str());
253 
254  // Chance to hit
255  const color_t cth_color = game_config::red_to_green(attacker.stats_.chance_to_hit);
256 
257  ss.str("");
258  ss << font::span_color(cth_color) << attacker.stats_.chance_to_hit << "%</span>";
259 
260  set_label_helper("chance_to_hit", ss.str());
261 }
262 
263 void attack_predictions::draw_hp_graph(drawing& hp_graph, const combatant_data& attacker, const combatant_data& defender)
264 {
265  // Font size. If you change this, you must update the separator space.
266  // TODO: probably should remove this.
267  const int fs = font::SIZE_SMALL;
268 
269  // Space before HP separator.
270  const int hp_sep = 30;
271 
272  // Space after percentage separator.
273  const int percent_sep = 50;
274 
275  // Bar space between both separators.
276  const int bar_space = graph_width - hp_sep - percent_sep - 4;
277 
278  // Set some variables for the WML portion of the graph to use.
279  canvas& hp_graph_canvas = hp_graph.get_drawing_canvas();
280 
281  hp_graph_canvas.set_variable("hp_column_width", wfl::variant(hp_sep));
282  hp_graph_canvas.set_variable("chance_column_width", wfl::variant(percent_sep));
283 
284  config cfg, shape;
285 
286  int i = 0;
287 
288  // Draw the rows (lower HP values are at the bottom).
289  for(const auto& probability : get_hitpoint_probabilities(attacker.combatant_.hp_dist)) {
290 
291  // Get the HP and probability.
292  int hp; double prob;
293  std::tie(hp, prob) = probability;
294 
295  color_t row_color;
296 
297  // Death line is red.
298  if(hp == 0) {
299  row_color = {229, 0, 0};
300  }
301 
302  // Below current hitpoints value is orange.
303  else if(hp < static_cast<int>(attacker.stats_.hp)) {
304  // Stone is grey.
305  if(defender.stats_.petrifies) {
306  row_color = {154, 154, 154};
307  } else {
308  row_color = {244, 201, 0};
309  }
310  }
311 
312  // Current hitpoints value and above is green.
313  else {
314  row_color = {8, 202, 0};
315  }
316 
317  shape["text"] = hp;
318  shape["x"] = 4;
319  shape["y"] = 2 + (fs + 2) * i;
320  shape["w"] = "(text_width)";
321  shape["h"] = "(text_height)";
322  shape["font_size"] = 12;
323  shape["color"] = "255, 255, 255, 255";
324  shape["text_alignment"] = "(text_alignment)";
325 
326  cfg.add_child("text", shape);
327 
328  shape.clear();
329  shape["text"] = get_probability_string(prob);
330  shape["x"] = graph_width - percent_sep + 2;
331  shape["y"] = 2 + (fs + 2) * i;
332  shape["w"] = "(text_width)";
333  shape["h"] = "(text_height)";
334  shape["font_size"] = 12;
335  shape["color"] = "255, 255, 255, 255";
336  shape["text_alignment"] = "(text_alignment)";
337 
338  cfg.add_child("text", shape);
339 
340  const int bar_len = std::max(static_cast<int>((prob * (bar_space - 4)) + 0.5), 2);
341 
342  const SDL_Rect bar_rect_1 {
343  hp_sep + 4,
344  6 + (fs + 2) * i,
345  bar_len,
346  8
347  };
348 
349  shape.clear();
350  shape["x"] = bar_rect_1.x;
351  shape["y"] = bar_rect_1.y;
352  shape["w"] = bar_rect_1.w;
353  shape["h"] = bar_rect_1.h;
354  shape["fill_color"] = row_color.to_rgba_string();
355 
356  cfg.add_child("rectangle", shape);
357 
358  ++i;
359  }
360 
361  hp_graph.append_drawing_data(cfg);
362 }
363 
365 {
366  hp_probability_vector res, temp_vec;
367 
368  // First, extract any relevant probability values
369  for(int i = 0; i < static_cast<int>(hp_dist.size()); ++i) {
370  const double prob = hp_dist[i];
371 
372  // We keep only values above 0.1%.
373  if(prob > 0.001) {
374  temp_vec.emplace_back(i, prob);
375  }
376  }
377 
378  // Then sort by descending probability.
379  std::sort(temp_vec.begin(), temp_vec.end(), [](const hp_probability_t& pair1, const hp_probability_t& pair2) {
380  return pair1.second > pair2.second;
381  });
382 
383  // Take only the highest probability values.;
384  std::copy_n(temp_vec.begin(), std::min<int>(graph_max_rows, temp_vec.size()), std::back_inserter(res));
385 
386  // Then, we sort the hitpoint values in descending order.
387  std::sort(res.begin(), res.end(), [](const hp_probability_t& pair1, const hp_probability_t& pair2) {
388  return pair1.first > pair2.first;
389  });
390 
391  return res;
392 }
393 
394 } // namespace dialogs
395 } // 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
std::string to_rgba_string() const
Returns the stored color as an "R,G,B,A" string.
Definition: color.cpp:110
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< hp_probability_t > hp_probability_vector
This class represents a single unit of a specific type.
Definition: unit.hpp:99
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:140
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:816
Definitions for the interface to Wesnoth Markup Language (WML).
unit_type::ALIGNMENT alignment() const
The alignment of this unit.
Definition: unit.hpp:391
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:72
Generic file dialog.
Definition: field-fwd.hpp:22
std::pair< int, map_location > under_leadership(const unit_map &units, const map_location &loc, const_attack_ptr weapon, const_attack_ptr opp_weapon)
Tests if the unit at loc is currently affected by leadership.
Definition: attack.cpp:1619
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
int damage_from(const attack_type &attack, bool attacker, const map_location &loc, const_attack_ptr weapon=nullptr) const
Calculates the damage this unit would take from a certain attack.
Definition: unit.hpp:849
const t_string id
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.
std::size_t i
Definition: function.cpp:933
The user set the widget invisible, that means:
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
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
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1071
config & add_child(config_key_type key)
Definition: config.cpp:479
The paths manager is responsible for recording the various paths that binary files may be located at...
Definition: fs_commit.cpp:37
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...
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1174
symbol_table string_table
Definition: language.cpp:63
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
bool is_attacker
True if the unit is the attacker.
Definition: attack.hpp:51
#define e
static const unsigned int graph_height
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:68
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&#39;s damage should be multiplied by due to the current time of day...
Definition: attack.cpp:1630