The Battle for Wesnoth  1.19.5+dev
statistics.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
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 /**
17  * @file
18  * Manage statistics: recruitments, recalls, kills, losses, etc.
19  */
20 
21 #include "statistics.hpp"
22 #include "game_board.hpp"
23 #include "log.hpp"
24 #include "resources.hpp" // Needed for teams, to get team save_id for a unit
25 #include "team.hpp" // Needed to get team save_id
26 #include "units/types.hpp"
27 #include "units/unit.hpp"
28 
29 #include <cmath>
30 
31 static lg::log_domain log_engine("engine");
32 #define DBG_NG LOG_STREAM(debug, log_engine)
33 #define ERR_NG LOG_STREAM(err, log_engine)
34 
35 namespace
36 {
37 
38 std::string get_team_save_id(const unit& u)
39 {
40  assert(resources::gameboard);
42 }
43 
44 }
45 
47  : record_(record)
48 {
49 
50 }
51 
53  statistics_t& stats, const unit& a, const unit& d, int a_cth, int d_cth)
54  : stats_(&stats)
55  , attacker_type(a.type_id())
56  , defender_type(d.type_id())
57  , attacker_side(get_team_save_id(a))
58  , defender_side(get_team_save_id(d))
59  , chance_to_hit_defender(a_cth)
60  , chance_to_hit_attacker(d_cth)
61  , attacker_res()
62  , defender_res()
63 {
64 }
65 
67 {
68  std::string attacker_key = "s" + attacker_res;
69  std::string defender_key = "s" + defender_res;
70 
73 
76 }
77 
79 {
81 }
82 
84 {
86 }
87 
88 void statistics_attack_context::attack_expected_damage(double attacker_inflict_, double defender_inflict_)
89 {
90  int attacker_inflict = std::round(attacker_inflict_ * stats::decimal_shift);
91  int defender_inflict = std::round(defender_inflict_ * stats::decimal_shift);
92  stats &att_stats = attacker_stats(), &def_stats = defender_stats();
93  att_stats.expected_damage_inflicted += attacker_inflict;
94  att_stats.expected_damage_taken += defender_inflict;
95  def_stats.expected_damage_inflicted += defender_inflict;
96  def_stats.expected_damage_taken += attacker_inflict;
97  att_stats.turn_expected_damage_inflicted += attacker_inflict;
98  att_stats.turn_expected_damage_taken += defender_inflict;
99  def_stats.turn_expected_damage_inflicted += defender_inflict;
100  def_stats.turn_expected_damage_taken += attacker_inflict;
101 }
102 
103 void statistics_attack_context::attack_result(hit_result res, int cth, int damage, int drain)
104 {
105  attacker_res.push_back(res == MISSES ? '0' : '1');
106  stats &att_stats = attacker_stats(), &def_stats = defender_stats();
107 
108  if(res != MISSES) {
109  ++att_stats.by_cth_inflicted[cth].hits;
110  ++att_stats.turn_by_cth_inflicted[cth].hits;
111  ++def_stats.by_cth_taken[cth].hits;
112  ++def_stats.turn_by_cth_taken[cth].hits;
113  }
114  ++att_stats.by_cth_inflicted[cth].strikes;
115  ++att_stats.turn_by_cth_inflicted[cth].strikes;
116  ++def_stats.by_cth_taken[cth].strikes;
117  ++def_stats.turn_by_cth_taken[cth].strikes;
118 
119  if(res != MISSES) {
120  // handle drain
121  att_stats.damage_taken -= drain;
122  def_stats.damage_inflicted -= drain;
123  att_stats.turn_damage_taken -= drain;
124  def_stats.turn_damage_inflicted -= drain;
125 
126  att_stats.damage_inflicted += damage;
127  def_stats.damage_taken += damage;
128  att_stats.turn_damage_inflicted += damage;
129  def_stats.turn_damage_taken += damage;
130  }
131 
132  if(res == KILLS) {
133  ++att_stats.killed[defender_type];
134  ++def_stats.deaths[defender_type];
135  }
136 }
137 
138 void statistics_attack_context::defend_result(hit_result res, int cth, int damage, int drain)
139 {
140  defender_res.push_back(res == MISSES ? '0' : '1');
141  stats &att_stats = attacker_stats(), &def_stats = defender_stats();
142 
143  if(res != MISSES) {
144  ++def_stats.by_cth_inflicted[cth].hits;
145  ++def_stats.turn_by_cth_inflicted[cth].hits;
146  ++att_stats.by_cth_taken[cth].hits;
147  ++att_stats.turn_by_cth_taken[cth].hits;
148  }
149  ++def_stats.by_cth_inflicted[cth].strikes;
150  ++def_stats.turn_by_cth_inflicted[cth].strikes;
151  ++att_stats.by_cth_taken[cth].strikes;
152  ++att_stats.turn_by_cth_taken[cth].strikes;
153 
154  if(res != MISSES) {
155  //handle drain
156  def_stats.damage_taken -= drain;
157  att_stats.damage_inflicted -= drain;
158  def_stats.turn_damage_taken -= drain;
159  att_stats.turn_damage_inflicted -= drain;
160 
161  att_stats.damage_taken += damage;
162  def_stats.damage_inflicted += damage;
163  att_stats.turn_damage_taken += damage;
164  def_stats.turn_damage_inflicted += damage;
165  }
166 
167  if(res == KILLS) {
168  ++att_stats.deaths[attacker_type];
169  ++def_stats.killed[attacker_type];
170  }
171 }
172 
174 {
175  stats& s = get_stats(get_team_save_id(u));
176  s.recruits[u.type().parent_id()]++;
177  s.recruit_cost += u.cost();
178 }
179 
181 {
182  stats& s = get_stats(get_team_save_id(u));
183  s.recalls[u.type_id()]++;
184  s.recall_cost += u.cost();
185 }
186 
188 {
189  stats& s = get_stats(get_team_save_id(u));
190  s.recalls[u.type_id()]--;
191  s.recall_cost -= u.cost();
192 }
193 
195 {
196  stats& s = get_stats(get_team_save_id(u));
197  s.recruits[u.type().parent_id()]--;
198  s.recruit_cost -= u.cost();
199 }
200 
202 {
203  stats& s = get_stats(get_team_save_id(u));
204  s.advanced_to[u.type_id()]++;
205 }
206 
207 void statistics_t::reset_turn_stats(const std::string& save_id)
208 {
209  stats& s = get_stats(save_id);
210  s.turn_damage_inflicted = 0;
211  s.turn_damage_taken = 0;
212  s.turn_expected_damage_inflicted = 0;
213  s.turn_expected_damage_taken = 0;
214  s.turn_by_cth_inflicted.clear();
215  s.turn_by_cth_taken.clear();
216  s.save_id = save_id;
217 }
218 
220 {
221  stats res;
222 
223  DBG_NG << "calculate_stats, side: " << save_id << " master_stats.size: " << master_stats().size();
224  // The order of this loop matters since the turn stats are taken from the
225  // last stats merged.
226  for(std::size_t i = 0; i != master_stats().size(); ++i) {
227  auto find_it = master_stats()[i].team_stats.find(save_id);
228  if(find_it != master_stats()[i].team_stats.end()) {
229  res.merge_with(find_it->second);
230  }
231  }
232 
233  return res;
234 }
235 
236 /**
237  * Returns a list of names and stats for each scenario in the current campaign.
238  * The front of the list is the oldest scenario; the back of the list is the
239  * (most) current scenario.
240  * Only scenarios with stats for the given @a side_id are included, but if no
241  * scenarios are applicable, then a vector containing a single dummy entry will
242  * be returned. (I.e., this never returns an empty vector.)
243  * This list is intended for the statistics dialog and may become invalid if
244  * new stats are recorded.
245  */
247 {
248  static const stats null_stats;
249  static const std::string null_name("");
250 
251  levels level_list;
252 
253  for(std::size_t level = 0; level != master_stats().size(); ++level) {
254  const auto& team_stats = master_stats()[level].team_stats;
255 
256  auto find_it = team_stats.find(save_id);
257  if(find_it != team_stats.end()) {
258  level_list.emplace_back(&master_stats()[level].scenario_name, &find_it->second);
259  }
260  }
261 
262  // Make sure we do return something (so other code does not have to deal
263  // with an empty list).
264  if(level_list.empty()) {
265  level_list.emplace_back(&null_name, &null_stats);
266  }
267 
268  return level_list;
269 }
270 
271 statistics_t::stats& statistics_t::get_stats(const std::string& save_id)
272 {
273  if(master_stats().empty()) {
274  master_stats().emplace_back(std::string());
275  }
276 
277  return master_stats().back().team_stats[save_id];
278 }
279 
280 int statistics_t::sum_str_int_map(const std::map<std::string, int>& m)
281 {
282  int res = 0;
283  for(const auto& pair: m) {
284  res += pair.second;
285  }
286 
287  return res;
288 }
289 
290 int statistics_t::sum_cost_str_int_map(const std::map<std::string, int>& m)
291 {
292  int cost = 0;
293  for(const auto& pair : m) {
294  const unit_type* t = unit_types.find(pair.first);
295  if(!t) {
296  ERR_NG << "Statistics refer to unknown unit type '" << pair.first << "'. Discarding.";
297  } else {
298  cost += pair.second * t->cost();
299  }
300  }
301 
302  return cost;
303 }
double t
Definition: astarsearch.cpp:63
team & get_team(int i)
Definition: game_board.hpp:92
levels level_stats(const std::string &save_id)
Returns a list of names and stats for each scenario in the current campaign.
Definition: statistics.cpp:246
void recall_unit(const unit &u)
Definition: statistics.cpp:180
std::vector< std::pair< const std::string *, const stats * > > levels
Stats (and name) for each scenario.
Definition: statistics.hpp:42
stats & get_stats(const std::string &save_id)
returns the stats for the given side in the current scenario.
Definition: statistics.cpp:271
void advance_unit(const unit &u)
Definition: statistics.cpp:201
static int sum_cost_str_int_map(const std::map< std::string, int > &m)
Definition: statistics.cpp:290
statistics_t(statistics_record::campaign_stats_t &record)
Definition: statistics.cpp:46
static int sum_str_int_map(const std::map< std::string, int > &m)
Definition: statistics.cpp:280
void un_recall_unit(const unit &u)
Definition: statistics.cpp:187
void reset_turn_stats(const std::string &save_id)
Definition: statistics.cpp:207
stats calculate_stats(const std::string &save_id)
Definition: statistics.cpp:219
void un_recruit_unit(const unit &u)
Definition: statistics.cpp:194
auto & master_stats()
Definition: statistics.hpp:53
void recruit_unit(const unit &u)
Definition: statistics.cpp:173
std::string save_id_or_number() const
Definition: team.hpp:218
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1265
A single unit type that the player may recruit.
Definition: types.hpp:43
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
Definition: types.hpp:145
This class represents a single unit of a specific type.
Definition: unit.hpp:133
std::size_t i
Definition: function.cpp:1028
int cost() const
How much gold is required to recruit this unit.
Definition: unit.hpp:633
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1932
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:355
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
Standard logging facilities (interface).
game_board * gameboard
Definition: resources.cpp:20
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: statistics.cpp:33
#define DBG_NG
Definition: statistics.cpp:32
void attack_result(hit_result res, int cth, int damage, int drain)
Definition: statistics.cpp:103
statistics_attack_context(statistics_t &stats, const unit &a, const unit &d, int a_cth, int d_cth)
Definition: statistics.cpp:52
statistics_t * stats_
never nullptr
Definition: statistics.hpp:72
void attack_expected_damage(double attacker_inflict, double defender_inflict)
Definition: statistics.cpp:88
void defend_result(hit_result res, int cth, int damage, int drain)
Definition: statistics.cpp:138
void merge_with(const stats_t &other)
battle_result_map attacks_inflicted
Statistics of this side's attacks on its own turns.
battle_result_map defends_inflicted
Statistics of this side's attacks on enemies' turns.
battle_result_map attacks_taken
Statistics of enemies' counter attacks on this side's turns.
battle_result_map defends_taken
Statistics of enemies' attacks against this side on their turns.
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1500
#define d