The Battle for Wesnoth  1.19.0-dev
advancement.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2024
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 /**
16  * @file
17  * Fighting.
18  */
19 
20 #include "actions/advancement.hpp"
21 
22 #include "actions/vision.hpp"
23 
24 #include "ai/manager.hpp" // for manager, holder
26 #include "game_events/pump.hpp"
27 #include "preferences/game.hpp"
28 #include "game_data.hpp" //resources::gamedata->phase()
29 #include "gettext.hpp"
31 #include "log.hpp"
32 #include "play_controller.hpp" //resources::controller
33 #include "random.hpp"
34 #include "resources.hpp"
35 #include "statistics.hpp"
36 #include "synced_user_choice.hpp"
37 #include "units/unit.hpp"
38 #include "units/types.hpp"
40 #include "units/helper.hpp" //number_of_possible_advances
41 #include "video.hpp"
42 #include "whiteboard/manager.hpp"
43 
44 static lg::log_domain log_engine("engine");
45 #define DBG_NG LOG_STREAM(debug, log_engine)
46 #define LOG_NG LOG_STREAM(info, log_engine)
47 #define WRN_NG LOG_STREAM(err, log_engine)
48 #define ERR_NG LOG_STREAM(err, log_engine)
49 
50 static lg::log_domain log_config("config");
51 #define LOG_CF LOG_STREAM(info, log_config)
52 
53 static lg::log_domain log_display("display");
54 #define LOG_DP LOG_STREAM(info, log_display)
55 
56 
57 namespace
58 {
59  int advance_unit_dialog(const map_location &loc)
60  {
61  const auto u_it = resources::gameboard->units().find(loc);
62  if(!u_it) {
63  ERR_NG << "advance_unit_dialog: unit not found";
64  return 0;
65  }
66  const unit& u = *u_it;
67  std::vector<unit_const_ptr> previews;
68 
69  for (const std::string& advance : u.advances_to()) {
70  preferences::encountered_units().insert(advance);
71  previews.push_back(get_advanced_unit(u, advance));
72  }
73 
74  std::size_t num_real_advances = previews.size();
75  bool always_display = false;
76 
77  for (const config& advance : u.get_modification_advances()) {
78  if (advance["always_display"]) {
79  always_display = true;
80  }
81  previews.push_back(get_amla_unit(u, advance));
82  }
83 
84  if (previews.size() > 1 || always_display) {
85  gui2::dialogs::unit_advance dlg(previews, num_real_advances);
86 
87  if(dlg.show()) {
88  return dlg.get_selected_index();
89  }
90 
91  // This should be unreachable, since canceling is disabled for the dialog
92  assert(false && "Unit advance dialog was cancelled, which should be impossible.");
93  }
94 
95  return 0;
96  }
97 
98  bool animate_unit_advancement(const map_location &loc, std::size_t choice, const bool &fire_event, const bool animate)
99  {
100  const events::command_disabler cmd_disabler;
101 
103  if (u == resources::gameboard->units().end()) {
104  LOG_DP << "animate_unit_advancement suppressed: invalid unit";
105  return false;
106  }
107  else if (!u->advances()) {
108  LOG_DP << "animate_unit_advancement suppressed: unit does not advance";
109  return false;
110  }
111 
112  const std::vector<std::string>& options = u->advances_to();
113  std::vector<config> mod_options = u->get_modification_advances();
114 
115  assert(options.size() + mod_options.size() > 0);
116  if (choice >= options.size() + mod_options.size()) {
117  LOG_DP << "animate_unit_advancement: invalid option, using first option";
118  choice = 0;
119  }
120 
121  // When the unit advances, it fades to white, and then switches
122  // to the new unit, then fades back to the normal color
123 
124  if (animate && !video::headless() && !resources::controller->is_skipping_replay()) {
125  unit_animator animator;
126  bool with_bars = true;
127  animator.add_animation(u.get_shared_ptr(), "levelout", u->get_location(), map_location(), 0, with_bars);
128  animator.start_animations();
129  animator.wait_for_end();
130  }
131 
132  if (choice < options.size()) {
133  // chosen_unit is not a reference, since the unit may disappear at any moment.
134  std::string chosen_unit = options[choice];
135  ::advance_unit(loc, chosen_unit, fire_event);
136  }
137  else {
138  const config &mod_option = mod_options[choice - options.size()];
139  ::advance_unit(loc, &mod_option, fire_event);
140  }
141 
142  u = resources::gameboard->units().find(loc);
144 
145  if (animate && u != resources::gameboard->units().end() && !video::headless() && !resources::controller->is_skipping_replay()) {
146  unit_animator animator;
147  animator.add_animation(u.get_shared_ptr(), "levelin", u->get_location(), map_location(), 0, true);
148  animator.start_animations();
149  animator.wait_for_end();
150  animator.set_all_standing();
152  events::pump();
153  }
154 
156 
157  return true;
158  }
159 
160  int get_advancement_index(const unit& u, const std::string& id)
161  {
162  const std::vector<std::string>& type_options = u.advances_to();
163  {
164  auto pick_iter = std::find(type_options.begin(), type_options.end(), id);
165  if(pick_iter != type_options.end()) {
166  return std::distance(type_options.begin(), pick_iter);
167  }
168  }
169  {
170  auto amla_options = u.get_modification_advances();
171  auto pick_iter = std::find_if(amla_options.begin(), amla_options.end(), [&](const config& adv){
172  return adv["id"].str() == id;
173  });
174  if(pick_iter != amla_options.end()) {
175  return type_options.size() + std::distance(amla_options.begin(), pick_iter);
176  }
177  }
178  return -1;
179  }
180 
181  class unit_advancement_choice : public mp_sync::user_choice
182  {
183  public:
184  unit_advancement_choice(const map_location& loc, int total_opt, int side_num, bool force_dialog)
185  : loc_ (loc), nb_options_(total_opt), side_num_(side_num), force_dialog_(force_dialog)
186  {
187  }
188 
189  virtual ~unit_advancement_choice()
190  {
191  }
192 
193  virtual config query_user(int /*side*/) const
194  {
195  //the 'side' parameter might differ from side_num_-
196  int res = 0;
197  team t = resources::gameboard->get_team(side_num_);
198  //i wonder how this got included here ?
199  bool is_mp = resources::controller->is_networked_mp();
200  bool is_current_side = resources::controller->current_side() == side_num_;
201  //note, that the advancements for networked sides are also determined on the current playing side.
202 
203  //to make mp games equal we only allow selecting advancements to the current side.
204  //otherwise we'd give an unfair advantage to the side that hosts ai sides if units advance during ai turns.
205  if(!video::headless() && (force_dialog_ || (t.is_local_human() && !t.is_droid() && !t.is_idle() && (is_current_side || !is_mp))))
206  {
207  res = advance_unit_dialog(loc_);
208  }
209  else if(is_current_side && (t.is_local_ai() || t.is_network_ai() || t.is_empty()))
210  {
211  res = randomness::generator->get_random_int(0, nb_options_-1);
212 
214  //if ai_advancement_ is the default advancement the following code will
215  //have no effect because get_advancements returns an empty list.
217  if(!u) {
218  ERR_NG << "unit_advancement_choice: unit not found";
219  return config{};
220  }
221 
222  std::vector<std::string> allowed = ai_advancement.get_advancements(u);
223  for(const auto& adv_id : allowed) {
224  int res_new = get_advancement_index(*u, adv_id);
225  if(res_new != -1) {
226  // if the advancement ids were really unique we could also make this function return the
227  // advancements id instead of its index. But i dont think there are guaraenteed to be unique.
228  res = res_new;
229  break;
230  }
231  }
232  }
233  else
234  {
235  // we are in the situation, that the unit is owned by a human, but he's not allowed to do this decision.
236  // because it's a mp game and it's not his turn.
237  // default to the first unit listed in the unit's advancements
238  }
239  LOG_NG << "unit at position " << loc_ << " chose advancement number " << res;
240  config retv;
241  retv["value"] = res;
242  return retv;
243 
244  }
245  virtual config random_choice(int /*side*/) const
246  {
247  config retv;
248  retv["value"] = 0;
249  return retv;
250  }
251  virtual std::string description() const
252  {
253  // TRANSLATORS: In networked games, when one player has the choice
254  // between multiple advancements of a unit, this text is sent to
255  // other players. It will be embedded within a message.
256  return _("waiting for^an advancement choice");
257  }
258  private:
259  const map_location loc_;
260  int nb_options_;
261  int side_num_;
262  bool force_dialog_;
263  };
264 }
265 
266 /*
267 advances the unit and stores data in the replay (or reads data from replay).
268 */
270 {
271  //i just don't want infinite loops...
272  // the 20 is picked rather randomly.
273  for(int advacment_number = 0; advacment_number < 20; advacment_number++)
274  {
276  //this implies u.valid()
278  return;
279  }
280 
281  if(params.fire_events_)
282  {
283  LOG_NG << "Firing pre advance event at " << params.loc_ <<".";
284  resources::game_events->pump().fire("pre_advance", params.loc_);
285  //TODO: maybe use id instead of location here ?.
286  u = resources::gameboard->units().find(params.loc_);
288  {
289  LOG_NG << "pre advance event aborted advancing.";
290  return;
291  }
292  }
293  //we don't want to let side 1 decide it during start/prestart.
294  //The "0" parameter here is the default and gets resolves to "current player"
295  int side_for = resources::gamedata->has_current_player() ? 0: u->side();
297  unit_advancement_choice(params.loc_, unit_helper::number_of_possible_advances(*u), u->side(), params.force_dialog_), side_for);
298  //calls actions::advance_unit.
299  bool result = animate_unit_advancement(params.loc_, selected["value"], params.fire_events_, params.animate_);
300 
301  DBG_NG << "animate_unit_advancement result = " << result;
302  u = resources::gameboard->units().find(params.loc_);
303  // level 10 unit gives 80 XP and the highest mainline is level 5
304  if (u.valid() && u->experience() > 80)
305  {
306  WRN_NG << "Unit has too many (" << u->experience() << ") XP left; cascade leveling goes on still.";
307  }
308  }
309  ERR_NG << "unit at " << params.loc_ << " tried to advance more than 20 times. Advancing was aborted";
310 }
311 
312 unit_ptr get_advanced_unit(const unit &u, const std::string& advance_to)
313 {
314  const unit_type *new_type = unit_types.find(advance_to);
315  if (!new_type) {
316  throw game::game_error("Could not find the unit being advanced"
317  " to: " + advance_to);
318  }
319  unit_ptr new_unit = u.clone();
320  new_unit->set_experience(new_unit->experience_overflow());
321  new_unit->advance_to(*new_type);
322  new_unit->heal_fully();
323  new_unit->set_state(unit::STATE_POISONED, false);
324  new_unit->set_state(unit::STATE_SLOWED, false);
325  new_unit->set_state(unit::STATE_PETRIFIED, false);
326  new_unit->set_user_end_turn(false);
327  new_unit->set_hidden(false);
328  return new_unit;
329 }
330 
331 
332 /**
333  * Returns the AMLA-advanced version of a unit (with traits and items retained).
334  */
335 unit_ptr get_amla_unit(const unit &u, const config &mod_option)
336 {
337  unit_ptr amla_unit = u.clone();
338  amla_unit->set_experience(amla_unit->experience_overflow());
339  amla_unit->add_modification("advancement", mod_option);
340  return amla_unit;
341 }
342 
343 
344 void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
345 {
347  if(!u.valid()) {
348  return;
349  }
350  // original_type is not a reference, since the unit may disappear at any moment.
351  std::string original_type = u->type_id();
352 
353  // "advance" event.
354  if(fire_event)
355  {
356  LOG_NG << "Firing advance event at " << loc <<".";
357  resources::game_events->pump().fire("advance",loc);
358 
359  if (!u.valid() || u->experience() < u->max_experience() ||
360  u->type_id() != original_type)
361  {
362  LOG_NG << "WML has invalidated the advancing unit. Aborting.";
363  return;
364  }
365  // In case WML moved the unit:
366  loc = u->get_location();
367  }
368 
369  // This is not normally necessary, but if a unit loses power when leveling
370  // (e.g. loses "jamming" or ambush), it could be discovered as a result of
371  // the advancement.
372  std::vector<int> not_seeing = actions::get_sides_not_seeing(*u);
373 
374  // Create the advanced unit.
375  auto [new_unit, use_amla] = utils::visit(
376  [u](const auto& v) {
377  if constexpr(utils::decayed_is_same<std::string, decltype(v)>) {
378  return std::pair(get_advanced_unit(*u, v), false);
379  } else {
380  return std::pair(get_amla_unit(*u, *v), true);
381  }
382  },
383  advance_to);
384 
385  new_unit->set_location(loc);
386  if ( !use_amla )
387  {
389  preferences::encountered_units().insert(new_unit->type_id());
390  LOG_CF << "Added '" << new_unit->type_id() << "' to the encountered units.";
391  }
392  u->anim_comp().clear_haloes();
394  resources::whiteboard->on_kill_unit();
395  u = resources::gameboard->units().insert(new_unit).first;
396 
397  // Update fog/shroud.
398  actions::shroud_clearer clearer;
399  clearer.clear_unit(loc, *new_unit);
400 
401  // "post_advance" event.
402  if(fire_event)
403  {
404  LOG_NG << "Firing post_advance event at " << loc << ".";
405  resources::game_events->pump().fire("post_advance",loc);
406  }
407 
408  // "sighted" event(s).
409  clearer.fire_events();
410  if ( u.valid() )
411  actions::actor_sighted(*u, &not_seeing);
412 
413  resources::whiteboard->on_gamestate_change();
414 }
unit_ptr get_advanced_unit(const unit &u, const std::string &advance_to)
Returns the advanced version of a unit (with traits and items retained).
#define WRN_NG
Definition: advancement.cpp:47
void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
Function which will advance the unit at loc to 'advance_to'.
unit_ptr get_amla_unit(const unit &u, const config &mod_option)
Returns the AMLA-advanced version of a unit (with traits and items retained).
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: advancement.cpp:48
#define LOG_DP
Definition: advancement.cpp:54
void advance_unit_at(const advance_unit_params &params)
static lg::log_domain log_display("display")
#define DBG_NG
Definition: advancement.cpp:45
#define LOG_CF
Definition: advancement.cpp:51
#define LOG_NG
Definition: advancement.cpp:46
static lg::log_domain log_config("config")
Various functions that implement advancements of units.
utils::variant< std::string, const config * > advancement_option
Definition: advancement.hpp:62
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
Definition: astarsearch.cpp:63
Class to encapsulate fog/shroud clearing and the resultant sighted events.
Definition: vision.hpp:72
game_events::pump_result_t fire_events()
Fires the sighted events that were earlier recorded by fog/shroud clearing.
Definition: vision.cpp:541
bool clear_unit(const map_location &view_loc, team &view_team, std::size_t viewer_id, int sight_range, bool slowed, const movetype::terrain_costs &costs, const map_location &real_loc, const std::set< map_location > *known_units=nullptr, std::size_t *enemy_count=nullptr, std::size_t *friend_count=nullptr, move_unit_spectator *spectator=nullptr, bool instant=true)
Clears shroud (and fog) around the provided location for view_team based on sight_range,...
Definition: vision.cpp:330
static manager & get_singleton()
Definition: manager.hpp:142
const ai::unit_advancements_aspect & get_advancement_aspect_for_side(side_number side)
Definition: manager.cpp:715
const std::vector< std::string > get_advancements(const unit_map::const_iterator &unit) const
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3145
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3138
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
team & get_team(int i)
Definition: game_board.hpp:91
virtual const unit_map & units() const override
Definition: game_board.hpp:106
bool has_current_player() const
returns where there is currently a well defiend "current player", that is for example not the case du...
Definition: game_data.cpp:192
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
game_events::wml_event_pump & pump()
Definition: manager.cpp:253
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:399
bool show(const unsigned auto_close_time=0)
Shows the window.
statistics_t & statistics()
int current_side() const
Returns the number of the side whose turn it is.
virtual bool is_networked_mp() const
int get_random_int(int min, int max)
Definition: random.hpp:51
void advance_unit(const unit &u)
Definition: statistics.cpp:201
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
void wait_for_end() const
Definition: animation.cpp:1460
void add_animation(unit_const_ptr animated_unit, const unit_animation *animation, const map_location &src=map_location::null_location(), bool with_bars=false, const std::string &text="", const color_t text_color={0, 0, 0})
Definition: animation.cpp:1325
void start_animations()
Definition: animation.cpp:1395
void set_all_standing()
Definition: animation.cpp:1523
unit_iterator find(std::size_t id)
Definition: map.cpp:302
std::size_t erase(const map_location &l)
Erases the unit at location l, if any.
Definition: map.cpp:289
umap_retval_pair_t insert(unit_ptr p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:135
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:1267
A single unit type that the player may recruit.
Definition: types.hpp:43
This class represents a single unit of a specific type.
Definition: unit.hpp:133
unit_ptr clone() const
Definition: unit.hpp:221
map_location loc_
static std::string _(const char *str)
Definition: gettext.hpp:93
@ STATE_SLOWED
Definition: unit.hpp:860
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:862
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:861
std::vector< config > get_modification_advances() const
Gets any non-typed advanced options set by modifications.
Definition: unit.cpp:1912
const advances_to_t & advances_to() const
Gets the possible types this unit can advance to on level-up.
Definition: unit.hpp:244
Standard logging facilities (interface).
std::vector< int > get_sides_not_seeing(const unit &target)
Returns the sides that cannot currently see target.
Definition: vision.cpp:590
game_events::pump_result_t actor_sighted(const unit &target, const std::vector< int > *cache)
Fires sighted events for the sides that can see target.
Definition: vision.cpp:617
void pump()
Process all events currently in the queue.
Definition: events.cpp:478
std::string selected
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
config get_user_choice(const std::string &name, const user_choice &uch, int side=0)
std::set< std::string > & encountered_units()
Definition: game.cpp:913
const config & options()
Definition: game.cpp:552
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
game_board * gameboard
Definition: resources.cpp:20
game_data * gamedata
Definition: resources.cpp:22
game_events::manager * game_events
Definition: resources.cpp:24
play_controller * controller
Definition: resources.cpp:21
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
bool will_certainly_advance(const unit_map::iterator &u)
Encapsulates the logic for deciding whether an iterator u points to a unit that can advance.
Definition: helper.cpp:33
int number_of_possible_advances(const unit &u)
Determines the total number of available advancements (of any kind) for a given unit.
Definition: helper.cpp:28
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition: general.hpp:33
bool headless()
The game is running headless.
Definition: video.cpp:141
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
Define the game's event mechanism.
advances the unit at loc if it has enough experience, maximum 20 times.
Definition: advancement.hpp:39
map_location loc_
Definition: advancement.hpp:46
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
Encapsulates the map of the game.
Definition: location.hpp:38
Interface for querying local choices.
virtual config query_user(int side) const =0
virtual std::string description() const
virtual config random_choice(int side) const =0
bool valid() const
Definition: map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:217
unit_type_data unit_types
Definition: types.cpp:1486
Various functions implementing vision (through fog of war and shroud).