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/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, opp_weapon = defender.stats_.weapon;
130  auto ctx = weapon->specials_context(&attacker.unit_, &defender.unit_, attacker.unit_.get_location(), defender.unit_.get_location(), attacker.stats_.is_attacker, opp_weapon);
131  boost::optional<decltype(ctx)> opp_ctx;
132 
133  if(opp_weapon) {
134  opp_ctx.emplace(opp_weapon->specials_context(&defender.unit_, &attacker.unit_, defender.unit_.get_location(), attacker.unit_.get_location(), defender.stats_.is_attacker, weapon));
135  }
136 
137  // Get damage modifiers.
138  unit_ability_list dmg_specials = weapon->get_specials("damage");
139  unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), attacker.stats_.backstab_pos);
140 
141  // Get the SET damage modifier, if any.
142  auto set_dmg_effect = std::find_if(dmg_effect.begin(), dmg_effect.end(),
143  [](const unit_abilities::individual_effect& e) { return e.type == unit_abilities::SET; }
144  );
145 
146  // Either user the SET modifier or the base weapon damage.
147  if(set_dmg_effect == dmg_effect.end()) {
148  ss << weapon->damage() << " (<i>" << weapon->name() << "</i>)";
149  } else {
150  assert(set_dmg_effect->ability);
151  ss << set_dmg_effect->value << " (<i>" << (*set_dmg_effect->ability)["name"] << "</i>)";
152  }
153 
154  // Process the ADD damage modifiers.
155  for(const auto& e : dmg_effect) {
156  if(e.type == unit_abilities::ADD) {
157  ss << "\n";
158 
159  if(e.value >= 0) {
160  ss << '+';
161  }
162 
163  ss << e.value;
164  ss << " (<i>" << (*e.ability)["name"] << "</i>)";
165  }
166  }
167 
168  // Process the MUL damage modifiers.
169  for(const auto& e : dmg_effect) {
170  if(e.type == unit_abilities::MUL) {
171  ss << "\n";
172  ss << font::unicode_multiplication_sign << (e.value / 100);
173 
174  if(e.value % 100) {
175  ss << "." << ((e.value % 100) / 10);
176  if(e.value % 10) {
177  ss << (e.value % 10);
178  }
179  }
180 
181  ss << " (<i>" << (*e.ability)["name"] << "</i>)";
182  }
183  }
184 
185  set_label_helper("base_damage", ss.str());
186 
187  ss.str("");
188 
189  // Resistance modifier.
190  const int resistance_modifier = defender.unit_.damage_from(*weapon, !attacker.stats_.is_attacker, defender.unit_.get_location(), opp_weapon);
191  if(resistance_modifier != 100) {
192  if(attacker.stats_.is_attacker) {
193  ss << _("Defender resistance vs") << " ";
194  } else {
195  ss << _("Attacker vulnerability vs") << " ";
196  }
197 
198  ss << string_table["type_" + weapon->type()];
199 
200  set_label_helper("resis_label", ss.str());
201 
202  ss.str("");
203  ss << font::unicode_multiplication_sign << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
204 
205  set_label_helper("resis", ss.str());
206  }
207 
208  ss.str("");
209 
210  // TODO: color format the modifiers
211 
212  // Time of day modifier.
213  const unit& u = attacker.unit_;
214 
215  const int tod_modifier = combat_modifier(resources::gameboard->units(), resources::gameboard->map(),
216  u.get_location(), u.alignment(), u.is_fearless());
217 
218  if(tod_modifier != 0) {
219  set_label_helper("tod_modifier", utils::signed_percent(tod_modifier));
220  } else {
221  hide_label_helper("tod_modifier");
222  }
223 
224  // Leadership bonus.
225  const int leadership_bonus = under_leadership(resources::gameboard->units(), attacker.unit_.get_location(), weapon, opp_weapon).first;
226 
227  if(leadership_bonus != 0) {
228  set_label_helper("leadership_modifier", utils::signed_percent(leadership_bonus));
229  } else {
230  hide_label_helper("leadership_modifier");
231  }
232 
233  // Slowed penalty.
234  if(attacker.stats_.is_slowed) {
235  set_label_helper("slowed_modifier", "/ 2");
236  } else {
237  hide_label_helper("slowed_modifier");
238  }
239 
240  // Total damage.
241  const int base_damage = weapon->damage();
242 
243  color_t dmg_color = font::weapon_color;
244  if(attacker.stats_.damage > base_damage) {
245  dmg_color = font::good_dmg_color;
246  } else if(attacker.stats_.damage < base_damage) {
247  dmg_color = font::bad_dmg_color;
248  }
249 
250  ss << font::span_color(dmg_color) << attacker.stats_.damage << "</span>"
252 
253  set_label_helper("total_damage", ss.str());
254 
255  // Chance to hit
256  const color_t cth_color = game_config::red_to_green(attacker.stats_.chance_to_hit);
257 
258  ss.str("");
259  ss << font::span_color(cth_color) << attacker.stats_.chance_to_hit << "%</span>";
260 
261  set_label_helper("chance_to_hit", ss.str());
262 }
263 
264 void attack_predictions::draw_hp_graph(drawing& hp_graph, const combatant_data& attacker, const combatant_data& defender)
265 {
266  // Font size. If you change this, you must update the separator space.
267  // TODO: probably should remove this.
268  const int fs = font::SIZE_SMALL;
269 
270  // Space before HP separator.
271  const int hp_sep = 30;
272 
273  // Space after percentage separator.
274  const int percent_sep = 50;
275 
276  // Bar space between both separators.
277  const int bar_space = graph_width - hp_sep - percent_sep - 4;
278 
279  // Set some variables for the WML portion of the graph to use.
280  canvas& hp_graph_canvas = hp_graph.get_drawing_canvas();
281 
282  hp_graph_canvas.set_variable("hp_column_width", wfl::variant(hp_sep));
283  hp_graph_canvas.set_variable("chance_column_width", wfl::variant(percent_sep));
284 
285  config cfg, shape;
286 
287  int i = 0;
288 
289  // Draw the rows (lower HP values are at the bottom).
290  for(const auto& probability : get_hitpoint_probabilities(attacker.combatant_.hp_dist)) {
291 
292  // Get the HP and probability.
293  int hp; double prob;
294  std::tie(hp, prob) = probability;
295 
296  color_t row_color;
297 
298  // Death line is red.
299  if(hp == 0) {
300  row_color = {229, 0, 0};
301  }
302 
303  // Below current hitpoints value is orange.
304  else if(hp < static_cast<int>(attacker.stats_.hp)) {
305  // Stone is grey.
306  if(defender.stats_.petrifies) {
307  row_color = {154, 154, 154};
308  } else {
309  row_color = {244, 201, 0};
310  }
311  }
312 
313  // Current hitpoints value and above is green.
314  else {
315  row_color = {8, 202, 0};
316  }
317 
318  shape["text"] = hp;
319  shape["x"] = 4;
320  shape["y"] = 2 + (fs + 2) * i;
321  shape["w"] = "(text_width)";
322  shape["h"] = "(text_height)";
323  shape["font_size"] = 12;
324  shape["color"] = "255, 255, 255, 255";
325  shape["text_alignment"] = "(text_alignment)";
326 
327  cfg.add_child("text", shape);
328 
329  shape.clear();
330  shape["text"] = get_probability_string(prob);
331  shape["x"] = graph_width - percent_sep + 2;
332  shape["y"] = 2 + (fs + 2) * i;
333  shape["w"] = "(text_width)";
334  shape["h"] = "(text_height)";
335  shape["font_size"] = 12;
336  shape["color"] = "255, 255, 255, 255";
337  shape["text_alignment"] = "(text_alignment)";
338 
339  cfg.add_child("text", shape);
340 
341  const int bar_len = std::max(static_cast<int>((prob * (bar_space - 4)) + 0.5), 2);
342 
343  const SDL_Rect bar_rect_1 {
344  hp_sep + 4,
345  6 + (fs + 2) * i,
346  bar_len,
347  8
348  };
349 
350  shape.clear();
351  shape["x"] = bar_rect_1.x;
352  shape["y"] = bar_rect_1.y;
353  shape["w"] = bar_rect_1.w;
354  shape["h"] = bar_rect_1.h;
355  shape["fill_color"] = row_color.to_rgba_string();
356 
357  cfg.add_child("rectangle", shape);
358 
359  ++i;
360  }
361 
362  hp_graph.append_drawing_data(cfg);
363 }
364 
366 {
367  hp_probability_vector res, temp_vec;
368 
369  // First, extract any relevant probability values
370  for(int i = 0; i < static_cast<int>(hp_dist.size()); ++i) {
371  const double prob = hp_dist[i];
372 
373  // We keep only values above 0.1%.
374  if(prob > 0.001) {
375  temp_vec.emplace_back(i, prob);
376  }
377  }
378 
379  // Then sort by descending probability.
380  std::sort(temp_vec.begin(), temp_vec.end(), [](const hp_probability_t& pair1, const hp_probability_t& pair2) {
381  return pair1.second > pair2.second;
382  });
383 
384  // Take only the highest probability values.;
385  std::copy_n(temp_vec.begin(), std::min<int>(graph_max_rows, temp_vec.size()), std::back_inserter(res));
386 
387  // Then, we sort the hitpoint values in descending order.
388  std::sort(res.begin(), res.end(), [](const hp_probability_t& pair1, const hp_probability_t& pair2) {
389  return pair1.first > pair2.first;
390  });
391 
392  return res;
393 }
394 
395 } // namespace dialogs
396 } // 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: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:865
Definitions for the interface to Wesnoth Markup Language (WML).
unit_type::ALIGNMENT alignment() const
The alignment of this unit.
Definition: unit.hpp:396
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:1567
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:854
This file contains the settings handling of the widget library.
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:42
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
config & add_child(config_key_type key)
Definition: config.cpp:479
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1076
const std::string weapon_numbers_sep
Definition: constants.cpp:45
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:1184
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:92
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:63
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:1731