The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
statistics.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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
31static 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
35namespace
36{
37
38std::string get_team_save_id(const unit& u)
39{
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
82
87
88void 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
103void 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
138void 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
207void 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
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
280int 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
290int 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}
#define ERR_NG
#define DBG_NG
double t
team & get_team(int i)
levels level_stats(const std::string &save_id)
Returns a list of names and stats for each scenario in the current campaign.
void recall_unit(const unit &u)
std::vector< std::pair< const std::string *, const stats * > > levels
Stats (and name) for each scenario.
stats & get_stats(const std::string &save_id)
returns the stats for the given side in the current scenario.
auto & master_stats()
void advance_unit(const unit &u)
static int sum_cost_str_int_map(const std::map< std::string, int > &m)
statistics_t(statistics_record::campaign_stats_t &record)
static int sum_str_int_map(const std::map< std::string, int > &m)
void un_recall_unit(const unit &u)
void reset_turn_stats(const std::string &save_id)
stats calculate_stats(const std::string &save_id)
void un_recruit_unit(const unit &u)
void recruit_unit(const unit &u)
std::string save_id_or_number() const
Definition team.hpp:259
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:1253
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:149
int cost() const
Definition types.hpp:176
This class represents a single unit of a specific type.
Definition unit.hpp:39
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition unit.hpp:261
int cost() const
How much gold is required to recruit this unit.
Definition unit.hpp:555
const std::string & type_id() const
The id of this unit's type.
Definition unit.cpp:1964
int side() const
The side this unit belongs to.
Definition unit.hpp:249
Standard logging facilities (interface).
game_board * gameboard
Definition resources.cpp:20
static lg::log_domain log_engine("engine")
void attack_result(hit_result res, int cth, int damage, int drain)
statistics_attack_context(statistics_t &stats, const unit &a, const unit &d, int a_cth, int d_cth)
statistics_t * stats_
never nullptr
void attack_expected_damage(double attacker_inflict, double defender_inflict)
void defend_result(hit_result res, int cth, int damage, int drain)
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:1499
#define i
#define d