The Battle for Wesnoth  1.19.17+dev
aspect_attacks.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2025
3  by Yurii Chernyi <terraninfo@terraninfo.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  * Stage: fallback to other AI
18  * @file
19  */
20 
22 
23 #include "actions/attack.hpp"
24 #include "ai/manager.hpp"
25 #include "game_board.hpp"
26 #include "log.hpp"
27 #include "lua/wrapper_lauxlib.h"
28 #include "map/map.hpp"
29 #include "pathfind/pathfind.hpp"
30 #include "resources.hpp"
31 #include "scripting/lua_unit.hpp"
32 #include "team.hpp"
33 #include "units/filter.hpp"
34 #include "units/unit.hpp"
35 
36 namespace ai
37 {
38 namespace ai_default_rca
39 {
40 static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks");
41 #define DBG_AI LOG_STREAM(debug, log_ai_testing_aspect_attacks)
42 #define LOG_AI LOG_STREAM(info, log_ai_testing_aspect_attacks)
43 #define ERR_AI LOG_STREAM(err, log_ai_testing_aspect_attacks)
44 
45 aspect_attacks_base::aspect_attacks_base(readonly_context& context, const config& cfg, const std::string& id)
46  : typesafe_aspect<attacks_vector>(context, cfg, id)
47 {
48 }
49 
50 aspect_attacks::aspect_attacks(readonly_context& context, const config& cfg, const std::string& id)
51  : aspect_attacks_base(context, cfg, id)
52  , filter_own_()
53  , filter_enemy_()
54 {
55  if(auto filter_own = cfg.optional_child("filter_own")) {
56  vconfig vcfg(*filter_own);
57  vcfg.make_safe();
58  filter_own_.reset(new unit_filter(vcfg));
59  }
60 
61  if(auto filter_enemy = cfg.optional_child("filter_enemy")) {
62  vconfig vcfg(*filter_enemy);
63  vcfg.make_safe();
64  filter_enemy_.reset(new unit_filter(vcfg));
65  }
66 }
67 
69 {
70  this->value_ = analyze_targets();
71  this->valid_ = true;
72 }
73 
74 std::shared_ptr<attacks_vector> aspect_attacks_base::analyze_targets() const
75 {
76  const move_map& srcdst = get_srcdst();
77  const move_map& dstsrc = get_dstsrc();
78  const move_map& enemy_srcdst = get_enemy_srcdst();
79  const move_map& enemy_dstsrc = get_enemy_dstsrc();
80 
81  auto res = std::make_shared<attacks_vector>();
82  const unit_map& units_ = resources::gameboard->units();
83 
84  std::vector<map_location> unit_locs;
85  for(const unit& u : units_) {
86  if(u.side() == get_side() && u.attacks_left() && !(u.can_recruit() && is_passive_leader(u.id()))) {
87  if(!is_allowed_attacker(u)) {
88  continue;
89  }
90 
91  unit_locs.push_back(u.get_location());
92  }
93  }
94 
95  std::array<bool, 6> used_locations;
96  used_locations.fill(false);
97 
98  moves_map dummy_moves;
99  move_map fullmove_srcdst, fullmove_dstsrc;
100  calculate_possible_moves(dummy_moves, fullmove_srcdst, fullmove_dstsrc, false, true);
101 
102  unit_stats_cache().clear();
103 
104  for(const unit& u : units_) {
105  // Attack anyone who is on the enemy side,
106  // and who is not invisible or petrified.
107  if(current_team().is_enemy(u.side()) && !u.incapacitated() && !u.invisible(u.get_location())) {
108  if(!is_allowed_enemy(u)) {
109  continue;
110  }
111 
112  const auto adjacent = get_adjacent_tiles(u.get_location());
113  attack_analysis analysis;
114  analysis.target = u.get_location();
115  analysis.vulnerability = 0.0;
116  analysis.support = 0.0;
117 
118  do_attack_analysis(u.get_location(), srcdst, dstsrc, fullmove_srcdst, fullmove_dstsrc, enemy_srcdst,
119  enemy_dstsrc, adjacent, used_locations, unit_locs, *res, analysis, current_team());
120  }
121  }
122  return res;
123 }
124 
126  const move_map& srcdst,
127  const move_map& dstsrc,
128  const move_map& fullmove_srcdst,
129  const move_map& fullmove_dstsrc,
130  const move_map& enemy_srcdst,
131  const move_map& enemy_dstsrc,
132  const std::array<map_location, 6>& tiles,
133  std::array<bool, 6>& used_locations,
134  std::vector<map_location>& units,
135  std::vector<attack_analysis>& result,
136  attack_analysis& cur_analysis,
137  const team& current_team) const
138 {
139  // This function is called fairly frequently, so interact with the user here.
140 
142  const int max_attack_depth = 5;
143  if(cur_analysis.movements.size() >= std::size_t(max_attack_depth)) {
144  return;
145  }
146 
147  const gamemap& map_ = resources::gameboard->map();
148  unit_map& units_ = resources::gameboard->units();
149  const std::vector<team>& teams_ = resources::gameboard->teams();
150 
151  const std::size_t max_positions = 1000;
152  if(result.size() > max_positions && !cur_analysis.movements.empty()) {
153  LOG_AI << "cut analysis short with number of positions";
154  return;
155  }
156 
157  for(std::size_t i = 0; i != units.size(); ++i) {
158  const map_location current_unit = units[i];
159 
160  unit_map::iterator unit_itor = units_.find(current_unit);
161  assert(unit_itor != units_.end());
162 
163  // See if the unit has the backstab ability.
164  // Units with backstab will want to try to have a
165  // friendly unit opposite the position they move to.
166  //
167  // See if the unit has the slow ability -- units with slow only attack first.
168  bool backstab = false, slow = false;
169  for(const attack_type& a : unit_itor->attacks()) {
170  // For speed, just assume these specials will be active if they are present.
171  if(a.has_special("backstab", true)) {
172  backstab = true;
173  }
174 
175  if(a.has_special("slow", true)) {
176  slow = true;
177  }
178  }
179 
180  if(slow && cur_analysis.movements.empty() == false) {
181  continue;
182  }
183 
184  // Check if the friendly unit is surrounded,
185  // A unit is surrounded if it is flanked by enemy units
186  // and at least one other enemy unit is nearby
187  // or if the unit is totally surrounded by enemies
188  // with max. one tile to escape.
189  bool is_surrounded = false;
190  bool is_flanked = false;
191  int enemy_units_around = 0;
192  int accessible_tiles = 0;
193  const auto adj = get_adjacent_tiles(current_unit);
194 
195  for(std::size_t tile = 0; tile != 3; ++tile) {
196  const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
197  bool possible_flanked = false;
198 
199  if(map_.on_board(adj[tile])) {
200  ++accessible_tiles;
201  if(tmp_unit != units_.end() && current_team.is_enemy(tmp_unit->side())) {
202  ++enemy_units_around;
203  possible_flanked = true;
204  }
205  }
206 
207  const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
208  if(map_.on_board(adj[tile + 3])) {
209  ++accessible_tiles;
210  if(tmp_opposite_unit != units_.end() && current_team.is_enemy(tmp_opposite_unit->side())) {
211  ++enemy_units_around;
212  if(possible_flanked) {
213  is_flanked = true;
214  }
215  }
216  }
217  }
218 
219  if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1) {
220  is_surrounded = true;
221  }
222 
223  double best_vulnerability = 0.0, best_support = 0.0;
224  int best_rating = 0;
225  int cur_position = -1;
226 
227  // Iterate over positions adjacent to the unit, finding the best rated one.
228  for(unsigned j = 0; j < tiles.size(); ++j) {
229  // If in this planned attack, a unit is already in this location.
230  if(used_locations[j]) {
231  continue;
232  }
233 
234  // See if the current unit can reach that position.
235  if(tiles[j] != current_unit) {
236  auto its = dstsrc.equal_range(tiles[j]);
237  while(its.first != its.second) {
238  if(its.first->second == current_unit) {
239  break;
240  }
241 
242  ++its.first;
243  }
244 
245  // If the unit can't move to this location.
246  if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
247  continue;
248  }
249  }
250 
251  int best_leadership_bonus = under_leadership(*unit_itor, tiles[j]);
252  double leadership_bonus = static_cast<double>(best_leadership_bonus + 100) / 100.0;
253  if(leadership_bonus > 1.1) {
254  LOG_AI << unit_itor->name() << " is getting leadership " << leadership_bonus;
255  }
256 
257  // Check to see whether this move would be a backstab.
258  int backstab_bonus = 1;
259  double surround_bonus = 1.0;
260 
261  if(tiles[(j + 3) % 6] != current_unit) {
262  const unit_map::const_iterator itor = units_.find(tiles[(j + 3) % 6]);
263 
264  // Note that we *could* also check if a unit plans to move there
265  // before we're at this stage, but we don't because, since the
266  // attack calculations don't actually take backstab into account (too complicated),
267  // this could actually make our analysis look *worse* instead of better.
268  // So we only check for 'concrete' backstab opportunities.
269  // That would also break backstab_check, since it assumes
270  // the defender is in place.
271  if(itor != units_.end() && backstab_check(tiles[j], loc, units_, teams_)) {
272  if(backstab) {
273  backstab_bonus = 2;
274  }
275 
276  // No surround bonus if target is skirmisher
277  if(!itor->get_ability_bool("skirmisher")) {
278  surround_bonus = 1.2;
279  }
280  }
281  }
282 
283  // See if this position is the best rated we've seen so far.
284  int rating = static_cast<int>(rate_terrain(*unit_itor, tiles[j]) * backstab_bonus * leadership_bonus);
285  if(cur_position >= 0 && rating < best_rating) {
286  continue;
287  }
288 
289  // Find out how vulnerable we are to attack from enemy units in this hex.
290  // FIXME: suokko's r29531 multiplied this by a constant 1.5. ?
291  const double vulnerability = power_projection(tiles[j], enemy_dstsrc); //?
292 
293  // Calculate how much support we have on this hex from allies.
294  const double support = power_projection(tiles[j], fullmove_dstsrc); //?
295 
296  // If this is a position with equal defense to another position,
297  // but more vulnerability then we don't want to use it.
298  if(cur_position >= 0 && rating == best_rating
299  && vulnerability / surround_bonus - support * surround_bonus >= best_vulnerability - best_support) {
300  continue;
301  }
302 
303  cur_position = j;
304  best_rating = rating;
305  best_vulnerability = vulnerability / surround_bonus;
306  best_support = support * surround_bonus;
307  }
308 
309  if(cur_position != -1) {
310  units.erase(units.begin() + i);
311 
312  cur_analysis.movements.emplace_back(current_unit, tiles[cur_position]);
313  cur_analysis.vulnerability += best_vulnerability;
314  cur_analysis.support += best_support;
315  cur_analysis.is_surrounded = is_surrounded;
316  cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression());
317  result.push_back(cur_analysis);
318 
319  used_locations[cur_position] = true;
320 
321  do_attack_analysis(loc, srcdst, dstsrc, fullmove_srcdst, fullmove_dstsrc, enemy_srcdst, enemy_dstsrc, tiles,
322  used_locations, units, result, cur_analysis, current_team);
323 
324  used_locations[cur_position] = false;
325 
326  cur_analysis.vulnerability -= best_vulnerability;
327  cur_analysis.support -= best_support;
328  cur_analysis.movements.pop_back();
329 
330  units.insert(units.begin() + i, current_unit);
331  }
332  }
333 }
334 
336 {
337  const gamemap& map_ = resources::gameboard->map();
338  const int defense = u.defense_modifier(map_.get_terrain(loc));
339  int rating = 100 - defense;
340 
341  const int healing_value = 10;
342  const int friendly_village_value = 5;
343  const int neutral_village_value = 10;
344  const int enemy_village_value = 15;
345 
346  if(map_.gives_healing(loc) && u.get_ability_bool("regenerate", loc) == false) {
347  rating += healing_value;
348  }
349 
350  if(map_.is_village(loc)) {
351  int owner = resources::gameboard->village_owner(loc);
352 
353  if(owner == u.side()) {
354  rating += friendly_village_value;
355  } else if(owner == 0) {
356  rating += neutral_village_value;
357  } else {
358  rating += enemy_village_value;
359  }
360  }
361 
362  return rating;
363 }
364 
366 {
368  if(filter_own_ && !filter_own_->empty()) {
369  cfg.add_child("filter_own", filter_own_->to_config());
370  }
371 
372  if(filter_enemy_ && !filter_enemy_->empty()) {
373  cfg.add_child("filter_enemy", filter_enemy_->to_config());
374  }
375 
376  return cfg;
377 }
378 
380 {
381  if(u.side() != get_side()) {
382  return false;
383  }
384 
385  if(filter_own_) {
386  return (*filter_own_)(u);
387  }
388 
389  return true;
390 }
391 
393 {
394  const team& my_team = resources::gameboard->get_team(get_side());
395  if(!my_team.is_enemy(u.side())) {
396  return false;
397  }
398 
399  if(filter_enemy_) {
400  return (*filter_enemy_)(u);
401  }
402 
403  return true;
404 }
405 
406 } // namespace ai_default_rca
407 
409  readonly_context& context, const config& cfg, const std::string& id, std::shared_ptr<lua_ai_context>& l_ctx)
410  : aspect_attacks_base(context, cfg, id)
411  , handler_()
412  , code_()
413  , params_(cfg.child_or_empty("args"))
414 {
415  this->name_ = "lua_aspect";
416  if(cfg.has_attribute("code")) {
417  code_ = cfg["code"].str();
418  } else if(cfg.has_attribute("value")) {
419  code_ = "return " + cfg["value"].apply_visitor(lua_aspect_visitor());
420  } else {
421  // error
422  return;
423  }
424 
425  handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(code_.c_str(), *l_ctx));
426 }
427 
429 {
431  const config empty_cfg;
432  handler_->handle(params_, empty_cfg, true, obj_);
433 
434  aspect_attacks_lua_filter filt = *obj_->get();
435  aspect_attacks_base::recalculate();
436 
437  if(filt.lua) {
438  if(filt.ref_own_ != -1) {
439  luaL_unref(filt.lua, LUA_REGISTRYINDEX, filt.ref_own_);
440  }
441  if(filt.ref_enemy_ != -1) {
442  luaL_unref(filt.lua, LUA_REGISTRYINDEX, filt.ref_enemy_);
443  }
444  }
445 
446  obj_.reset();
447 }
448 
450 {
452  cfg["code"] = code_;
453  if(!params_.empty()) {
454  cfg.add_child("args", params_);
455  }
456 
457  return cfg;
458 }
459 
460 static bool call_lua_filter_fcn(lua_State* L, const unit& u, int idx)
461 {
462  lua_rawgeti(L, LUA_REGISTRYINDEX, idx);
464  luaW_pcall(L, 1, 1);
465  bool result = luaW_toboolean(L, -1);
466  lua_pop(L, 1);
467  return result;
468 }
469 
471 {
472  const aspect_attacks_lua_filter& filt = *obj_->get();
473  if(filt.lua && filt.ref_own_ != -1) {
474  return call_lua_filter_fcn(filt.lua, u, filt.ref_own_);
475  } else if(filt.filter_own_) {
476  return (*filt.filter_own_)(u);
477  } else {
478  return true;
479  }
480 }
481 
483 {
484  const aspect_attacks_lua_filter& filt = *obj_->get();
485  if(filt.lua && filt.ref_enemy_ != -1) {
486  return call_lua_filter_fcn(filt.lua, u, filt.ref_enemy_);
487  } else if(filt.filter_enemy_) {
488  return (*filt.filter_enemy_)(u);
489  } else {
490  return true;
491  }
492 }
493 
494 } // end of namespace ai
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:1486
bool backstab_check(const map_location &attacker_loc, const map_location &defender_loc, const unit_map &units, const std::vector< team > &teams)
Function to check if an attack will satisfy the requirements for backstab.
Definition: attack.cpp:1541
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
#define LOG_AI
Aspect: attacks.
std::shared_ptr< attacks_vector > analyze_targets() const
virtual bool is_allowed_enemy(const unit &u) const =0
void do_attack_analysis(const map_location &loc, const move_map &srcdst, const move_map &dstsrc, const move_map &fullmove_srcdst, const move_map &fullmove_dstsrc, const move_map &enemy_srcdst, const move_map &enemy_dstsrc, const std::array< map_location, 6 > &tiles, std::array< bool, 6 > &used_locations, std::vector< map_location > &units, std::vector< attack_analysis > &result, attack_analysis &cur_analysis, const team &current_team) const
aspect_attacks_base(readonly_context &context, const config &cfg, const std::string &id)
static int rate_terrain(const unit &u, const map_location &loc)
virtual bool is_allowed_attacker(const unit &u) const =0
std::shared_ptr< unit_filter > filter_own_
virtual bool is_allowed_attacker(const unit &u) const
virtual bool is_allowed_enemy(const unit &u) const
std::shared_ptr< unit_filter > filter_enemy_
aspect_attacks(readonly_context &context, const config &cfg, const std::string &id)
virtual config to_config() const
virtual bool is_allowed_enemy(const unit &u) const
std::shared_ptr< lua_object< aspect_attacks_lua_filter > > obj_
virtual bool is_allowed_attacker(const unit &u) const
virtual void recalculate() const
aspect_attacks_lua(readonly_context &context, const config &cfg, const std::string &id, std::shared_ptr< lua_ai_context > &l_ctx)
std::shared_ptr< lua_ai_action_handler > handler_
virtual config to_config() const
Definition: aspect.cpp:105
std::string name_
Definition: aspect.hpp:89
bool valid_
Definition: aspect.hpp:81
std::vector< std::pair< map_location, map_location > > movements
Definition: contexts.hpp:71
void analyze(const gamemap &map, unit_map &units, const readonly_context &ai_obj, const move_map &dstsrc, const move_map &srcdst, const move_map &enemy_dstsrc, double aggression)
Definition: attack.cpp:42
map_location target
Definition: contexts.hpp:70
double vulnerability
The vulnerability is the power projection of enemy units onto the hex we're standing on.
Definition: contexts.hpp:107
bool is_surrounded
Is true if the units involved in this attack sequence are surrounded.
Definition: contexts.hpp:116
void raise_user_interact()
Notifies all observers of 'ai_user_interact' event.
Definition: manager.cpp:424
static manager & get_singleton()
Definition: manager.hpp:140
virtual const team & current_team() const override
Definition: contexts.hpp:447
virtual const move_map & get_enemy_srcdst() const override
Definition: contexts.hpp:603
virtual const move_map & get_dstsrc() const override
Definition: contexts.hpp:588
virtual const move_map & get_srcdst() const override
Definition: contexts.hpp:708
virtual void calculate_possible_moves(std::map< map_location, pathfind::paths > &possible_moves, move_map &srcdst, move_map &dstsrc, bool enemy, bool assume_full_movement=false, const terrain_filter *remove_destinations=nullptr) const override
Definition: contexts.hpp:494
virtual bool is_passive_leader(const std::string &id) const override
Definition: contexts.hpp:758
virtual double power_projection(const map_location &loc, const move_map &dstsrc) const override
Function which finds how much 'power' a side can attack a certain location with.
Definition: contexts.hpp:673
virtual unit_stats_cache_t & unit_stats_cache() const override
Definition: contexts.hpp:858
virtual double get_aggression() const override
Definition: contexts.hpp:543
virtual const move_map & get_enemy_dstsrc() const override
Definition: contexts.hpp:593
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:393
std::shared_ptr< attacks_vector > value_
Definition: aspect.hpp:142
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
config & add_child(std::string_view key)
Definition: config.cpp:435
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:379
bool has_attribute(std::string_view key) const
Definition: config.cpp:156
bool empty() const
Definition: config.cpp:822
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
team & get_team(int i)
Definition: game_board.hpp:92
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:273
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:356
Encapsulates the map of the game.
Definition: map.hpp:173
bool is_village(const map_location &loc) const
Definition: map.cpp:66
int gives_healing(const map_location &loc) const
Definition: map.cpp:68
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool is_enemy(int n) const
Definition: team.hpp:267
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
This class represents a single unit of a specific type.
Definition: unit.hpp:136
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:164
const config * cfg
std::size_t i
Definition: function.cpp:1032
bool get_ability_bool(const std::string &tag_name, const map_location &loc) const
Checks whether this unit currently possesses or is affected by a given ability.
Definition: abilities.cpp:203
int side() const
The side this unit belongs to.
Definition: unit.hpp:346
std::size_t underlying_id() const
This unit's unique internal ID.
Definition: unit.hpp:395
int defense_modifier(const t_translation::terrain_code &terrain, const map_location &loc) const
The unit's defense on a given terrain.
Definition: unit.cpp:1756
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
Standard logging facilities (interface).
bool luaW_toboolean(lua_State *L, int n)
bool luaW_pcall(lua_State *L, int nArgs, int nRets, bool allow_wml_error)
Calls a Lua function stored below its nArgs arguments at the top of the stack.
lua_unit * luaW_pushunit(lua_State *L, Args... args)
Definition: lua_unit.hpp:116
static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks")
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:59
std::vector< attack_analysis > attacks_vector
Definition: game_info.hpp:51
std::multimap< map_location, map_location > move_map
The standard way in which a map of possible moves is recorded.
Definition: game_info.hpp:43
static bool call_lua_filter_fcn(lua_State *L, const unit &u, int idx)
std::map< map_location, pathfind::paths > moves_map
The standard way in which a map of possible movement routes to location is recorded.
Definition: game_info.hpp:46
static std::unique_ptr< class sdl_event_handler > handler_
Definition: handler.cpp:63
game_board * gameboard
Definition: resources.cpp:20
game_lua_kernel * lua_kernel
Definition: resources.cpp:25
This module contains various pathfinding functions and utilities.
std::shared_ptr< unit_filter > filter_own_
std::shared_ptr< unit_filter > filter_enemy_
Encapsulates the map of the game.
Definition: location.hpp:46