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