The Battle for Wesnoth  1.15.0-dev
advancement.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2018 by the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY.
10 
11  See the COPYING file for more details.
12 */
13 
14 /**
15  * @file
16  * Fighting.
17  */
18 
19 #include "actions/advancement.hpp"
20 
21 #include "actions/vision.hpp"
22 
24 #include "game_events/pump.hpp"
25 #include "preferences/game.hpp"
26 #include "game_data.hpp" //resources::gamedata->phase()
27 #include "gettext.hpp"
29 #include "log.hpp"
30 #include "play_controller.hpp" //resources::controller
31 #include "random.hpp"
32 #include "resources.hpp"
33 #include "statistics.hpp"
34 #include "synced_user_choice.hpp"
35 #include "units/unit.hpp"
36 #include "units/abilities.hpp"
38 #include "units/udisplay.hpp"
39 #include "units/helper.hpp" //number_of_possible_advances
40 #include "whiteboard/manager.hpp"
41 
42 static lg::log_domain log_engine("engine");
43 #define DBG_NG LOG_STREAM(debug, log_engine)
44 #define LOG_NG LOG_STREAM(info, log_engine)
45 #define WRN_NG LOG_STREAM(err, log_engine)
46 #define ERR_NG LOG_STREAM(err, log_engine)
47 
48 static lg::log_domain log_config("config");
49 #define LOG_CF LOG_STREAM(info, log_config)
50 
51 static lg::log_domain log_display("display");
52 #define LOG_DP LOG_STREAM(info, log_display)
53 
54 
55 namespace
56 {
57  int advance_unit_dialog(const map_location &loc)
58  {
59  const auto u_it = resources::gameboard->units().find(loc);
60  if(!u_it) {
61  ERR_NG << "advance_unit_dialog: unit not found\n";
62  return 0;
63  }
64  const unit& u = *u_it;
65  std::vector<unit_const_ptr> previews;
66 
67  for (const std::string& advance : u.advances_to()) {
68  preferences::encountered_units().insert(advance);
69  previews.push_back(get_advanced_unit(u, advance));
70  }
71 
72  std::size_t num_real_advances = previews.size();
73  bool always_display = false;
74 
75  for (const config& advance : u.get_modification_advances()) {
76  if (advance["always_display"]) {
77  always_display = true;
78  }
79  previews.push_back(get_amla_unit(u, advance));
80  }
81 
82  if (previews.size() > 1 || always_display) {
83  gui2::dialogs::unit_advance dlg(previews, num_real_advances);
84 
85  if(dlg.show()) {
86  return dlg.get_selected_index();
87  }
88 
89  // This should be unreachable, since canceling is disabled for the dialog
90  assert(false && "Unit advance dialog was cancelled, which should be impossible.");
91  }
92 
93  return 0;
94  }
95 
96  bool animate_unit_advancement(const map_location &loc, std::size_t choice, const bool &fire_event, const bool animate)
97  {
98  const events::command_disabler cmd_disabler;
99 
101  if (u == resources::gameboard->units().end()) {
102  LOG_DP << "animate_unit_advancement suppressed: invalid unit\n";
103  return false;
104  }
105  else if (!u->advances()) {
106  LOG_DP << "animate_unit_advancement suppressed: unit does not advance\n";
107  return false;
108  }
109 
110  const std::vector<std::string>& options = u->advances_to();
111  std::vector<config> mod_options = u->get_modification_advances();
112 
113  if (choice >= options.size() + mod_options.size()) {
114  LOG_DP << "animate_unit_advancement suppressed: invalid option\n";
115  return false;
116  }
117 
118  // When the unit advances, it fades to white, and then switches
119  // to the new unit, then fades back to the normal color
120 
121  if (animate && !CVideo::get_singleton().update_locked()) {
122  unit_animator animator;
123  bool with_bars = true;
124  animator.add_animation(&*u, "levelout", u->get_location(), map_location(), 0, with_bars);
125  animator.start_animations();
126  animator.wait_for_end();
127  }
128 
129  if (choice < options.size()) {
130  // chosen_unit is not a reference, since the unit may disappear at any moment.
131  std::string chosen_unit = options[choice];
132  ::advance_unit(loc, chosen_unit, fire_event);
133  }
134  else {
135  const config &mod_option = mod_options[choice - options.size()];
136  ::advance_unit(loc, &mod_option, fire_event);
137  }
138 
139  u = resources::gameboard->units().find(loc);
141 
142  if (animate && u != resources::gameboard->units().end() && !CVideo::get_singleton().update_locked()) {
143  unit_animator animator;
144  animator.add_animation(&*u, "levelin", u->get_location(), map_location(), 0, true);
145  animator.start_animations();
146  animator.wait_for_end();
147  animator.set_all_standing();
149  events::pump();
150  }
151 
153 
154  return true;
155  }
156 
157  class unit_advancement_choice : public mp_sync::user_choice
158  {
159  public:
160  unit_advancement_choice(const map_location& loc, int total_opt, int side_num, const ai::unit_advancements_aspect* ai_advancement, bool force_dialog)
161  : loc_ (loc), nb_options_(total_opt), side_num_(side_num), ai_advancement_(ai_advancement), force_dialog_(force_dialog)
162  {
163  }
164 
165  virtual ~unit_advancement_choice()
166  {
167  }
168 
169  virtual config query_user(int /*side*/) const
170  {
171  //the 'side' parameter might differ from side_num_-
172  int res = 0;
173  team t = resources::gameboard->get_team(side_num_);
174  //i wonder how this got included here ?
175  bool is_mp = resources::controller->is_networked_mp();
176  bool is_current_side = resources::controller->current_side() == side_num_;
177  //note, that the advancements for networked sides are also determined on the current playing side.
178 
179  //to make mp games equal we only allow selecting advancements to the current side.
180  //otherwise we'd give an unfair advantage to the side that hosts ai sides if units advance during ai turns.
181  if(!CVideo::get_singleton().non_interactive() && (force_dialog_ || (t.is_local_human() && !t.is_droid() && !t.is_idle() && (is_current_side || !is_mp))))
182  {
183  res = advance_unit_dialog(loc_);
184  }
185  else if(t.is_local_ai() || t.is_network_ai() || t.is_empty())
186  {
187  res = randomness::generator->get_random_int(0, nb_options_-1);
188 
189  //if ai_advancement_ is the default advancement the following code will
190  //have no effect because get_advancements returns an empty list.
191  if(ai_advancement_ != nullptr)
192  {
194  if(!u) {
195  ERR_NG << "unit_advancement_choice: unit not found\n";
196  return config{};
197  }
198  const std::vector<std::string>& options = u->advances_to();
199  const std::vector<std::string>& allowed = ai_advancement_->get_advancements(u);
200 
201  for(std::vector<std::string>::const_iterator a = options.begin(); a != options.end(); ++a) {
202  if (std::find(allowed.begin(), allowed.end(), *a) != allowed.end()){
203  res = std::distance(options.begin(), a);
204  break;
205  }
206  }
207  }
208 
209  }
210  else
211  {
212  //we are in the situation, that the unit is owned by a human, but he's not allowed to do this decision.
213  //because it's a mp game and it's not his turn.
214  //note that it doesn't matter whether we call randomness::generator->next_random() or rand().
215  res = randomness::generator->get_random_int(0, nb_options_-1);
216  }
217  LOG_NG << "unit at position " << loc_ << "choose advancement number " << res << "\n";
218  config retv;
219  retv["value"] = res;
220  return retv;
221 
222  }
223  virtual config random_choice(int /*side*/) const
224  {
225  config retv;
226  retv["value"] = 0;
227  return retv;
228  }
229  virtual std::string description() const
230  {
231  return _("an advancement choice");
232  }
233  private:
234  const map_location loc_;
235  int nb_options_;
236  int side_num_;
237  const ai::unit_advancements_aspect* ai_advancement_;
238  bool force_dialog_;
239  };
240 }
241 
242 /*
243 advances the unit and stores data in the replay (or reads data from replay).
244 */
246 {
247  //i just don't want infinite loops...
248  // the 20 is picked rather randomly.
249  for(int advacment_number = 0; advacment_number < 20; advacment_number++)
250  {
252  //this implies u.valid()
254  return;
255  }
256 
257  if(params.fire_events_)
258  {
259  LOG_NG << "Firing pre advance event at " << params.loc_ <<".\n";
260  resources::game_events->pump().fire("pre_advance", params.loc_);
261  //TODO: maybe use id instead of location here ?.
262  u = resources::gameboard->units().find(params.loc_);
264  {
265  LOG_NG << "pre advance event aborted advancing.\n";
266  return;
267  }
268  }
269  //we don't want to let side 1 decide it during start/prestart.
270  int side_for = resources::gamedata->phase() == game_data::PLAY ? 0: u->side();
272  unit_advancement_choice(params.loc_, unit_helper::number_of_possible_advances(*u), u->side(), params.ai_advancements_, params.force_dialog_), side_for);
273  //calls actions::advance_unit.
274  bool result = animate_unit_advancement(params.loc_, selected["value"], params.fire_events_, params.animate_);
275 
276  DBG_NG << "animate_unit_advancement result = " << result << std::endl;
277  u = resources::gameboard->units().find(params.loc_);
278  // level 10 unit gives 80 XP and the highest mainline is level 5
279  if (u.valid() && u->experience() > 80)
280  {
281  WRN_NG << "Unit has too many (" << u->experience() << ") XP left; cascade leveling goes on still." << std::endl;
282  }
283  }
284  ERR_NG << "unit at " << params.loc_ << "tried to advance more than 20 times. Advancing was aborted" << std::endl;
285 }
286 
287 unit_ptr get_advanced_unit(const unit &u, const std::string& advance_to)
288 {
289  const unit_type *new_type = unit_types.find(advance_to);
290  if (!new_type) {
291  throw game::game_error("Could not find the unit being advanced"
292  " to: " + advance_to);
293  }
294  unit_ptr new_unit = u.clone();
295  new_unit->set_experience(new_unit->experience_overflow());
296  new_unit->advance_to(*new_type);
297  new_unit->heal_fully();
298  new_unit->set_state(unit::STATE_POISONED, false);
299  new_unit->set_state(unit::STATE_SLOWED, false);
300  new_unit->set_state(unit::STATE_PETRIFIED, false);
301  new_unit->set_user_end_turn(false);
302  new_unit->set_hidden(false);
303  return new_unit;
304 }
305 
306 
307 /**
308  * Returns the AMLA-advanced version of a unit (with traits and items retained).
309  */
310 unit_ptr get_amla_unit(const unit &u, const config &mod_option)
311 {
312  unit_ptr amla_unit = u.clone();
313  amla_unit->set_experience(amla_unit->experience_overflow());
314  amla_unit->add_modification("advancement", mod_option);
315  return amla_unit;
316 }
317 
318 
319 void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
320 {
322  if(!u.valid()) {
323  return;
324  }
325  // original_type is not a reference, since the unit may disappear at any moment.
326  std::string original_type = u->type_id();
327 
328  // "advance" event.
329  if(fire_event)
330  {
331  LOG_NG << "Firing advance event at " << loc <<".\n";
332  resources::game_events->pump().fire("advance",loc);
333 
334  if (!u.valid() || u->experience() < u->max_experience() ||
335  u->type_id() != original_type)
336  {
337  LOG_NG << "WML has invalidated the advancing unit. Aborting.\n";
338  return;
339  }
340  // In case WML moved the unit:
341  loc = u->get_location();
342  }
343 
344  // This is not normally necessary, but if a unit loses power when leveling
345  // (e.g. loses "jamming" or ambush), it could be discovered as a result of
346  // the advancement.
347  std::vector<int> not_seeing = actions::get_sides_not_seeing(*u);
348 
349  // Create the advanced unit.
350  bool use_amla = boost::get<std::string>(&advance_to) == nullptr;
351  unit_ptr new_unit = use_amla ? get_amla_unit(*u, *boost::get<const config*>(advance_to)) :
352  get_advanced_unit(*u, boost::get<std::string>(advance_to));
353  new_unit->set_location(loc);
354  if ( !use_amla )
355  {
356  statistics::advance_unit(*new_unit);
357  preferences::encountered_units().insert(new_unit->type_id());
358  LOG_CF << "Added '" << new_unit->type_id() << "' to the encountered units.\n";
359  }
360  u->anim_comp().clear_haloes();
362  resources::whiteboard->on_kill_unit();
363  u = resources::gameboard->units().insert(new_unit).first;
364 
365  // Update fog/shroud.
366  actions::shroud_clearer clearer;
367  clearer.clear_unit(loc, *new_unit);
368 
369  // "post_advance" event.
370  if(fire_event)
371  {
372  LOG_NG << "Firing post_advance event at " << loc << ".\n";
373  resources::game_events->pump().fire("post_advance",loc);
374  }
375 
376  // "sighted" event(s).
377  clearer.fire_events();
378  if ( u.valid() )
379  actions::actor_sighted(*u, &not_seeing);
380 
381  resources::whiteboard->on_gamestate_change();
382 }
bool is_local_ai() const
Definition: team.hpp:267
play_controller * controller
Definition: resources.cpp:21
void wait_for_end() const
Definition: animation.cpp:1442
config get_user_choice(const std::string &name, const user_choice &uch, int side=0)
bool update_locked() const
Whether the screen has been &#39;locked&#39; or not.
Definition: video.cpp:357
bool is_empty() const
Definition: team.hpp:258
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:88
const ai::unit_advancements_aspect * ai_advancements_
Definition: advancement.hpp:48
#define WRN_NG
Definition: advancement.cpp:45
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:1275
void start_animations()
Definition: animation.cpp:1383
virtual const unit_map & units() const override
Definition: game_board.hpp:114
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2979
This class represents a single unit of a specific type.
Definition: unit.hpp:99
Various functions implementing vision (through fog of war and shroud).
umap_retval_pair_t insert(unit_ptr p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:135
#define a
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:30
void add_animation(const unit *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
bool is_idle() const
Definition: team.hpp:284
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
unit_ptr clone() const
Definition: unit.hpp:173
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:810
bool is_network_ai() const
Definition: team.hpp:269
unit_type_data unit_types
Definition: types.cpp:1452
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:811
#define ERR_NG
Definition: advancement.cpp:46
static CVideo & get_singleton()
Definition: video.hpp:43
virtual std::string description() const
std::size_t erase(const map_location &l)
Erases the unit at location l, if any.
Definition: map.cpp:298
map_location loc_
Definition: advancement.hpp:47
static lg::log_domain log_config("config")
bool show(const unsigned auto_close_time=0)
Shows the window.
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).
virtual config random_choice(int side) const =0
A single unit type that the player may recruit.
Definition: types.hpp:42
game_data * gamedata
Definition: resources.cpp:22
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).
#define DBG_NG
Definition: advancement.cpp:43
map_location loc_
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, costs, and slowed.
Definition: vision.cpp:330
boost::variant< std::string, const config *> advancement_option
Definition: advancement.hpp:64
const config & options()
Definition: game.cpp:568
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
team & get_team(int i)
Definition: game_board.hpp:104
game_events::pump_result_t fire_events()
Fires the sighted events that were earlier recorded by fog/shroud clearing.
Definition: vision.cpp:546
Various functions that implement advancements of units.
bool is_droid() const
Definition: team.hpp:283
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:809
game_board * gameboard
Definition: resources.cpp:20
Interface for querying local choices.
std::string selected
Error used for any general game error, e.g.
Definition: game_errors.hpp:46
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2969
game_events::manager * game_events
Definition: resources.cpp:24
virtual config query_user(int side) const =0
void pump()
Definition: events.cpp:425
advances the unit at loc if it has enough experience, maximum 20 times.
Definition: advancement.hpp:38
Encapsulates the map of the game.
Definition: location.hpp:42
unit_iterator find(std::size_t id)
Definition: map.cpp:311
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
void advance_unit(const unit &u)
Definition: statistics.cpp:540
void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
Function which will advance the unit at loc to &#39;advance_to&#39;.
virtual bool is_networked_mp() const
bool fire_event(const ui_event event, std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
#define LOG_CF
Definition: advancement.cpp:49
Define the game&#39;s event mechanism.
static lg::log_domain log_display("display")
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:25
std::set< std::string > & encountered_units()
Definition: game.cpp:940
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:51
void advance_unit_at(const advance_unit_params &params)
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
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:486
bool is_local_human() const
Definition: team.hpp:266
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
double t
Definition: astarsearch.cpp:64
bool find(E event, F functor)
Tests whether an event handler is available.
void set_all_standing()
Definition: animation.cpp:1499
std::vector< config > get_modification_advances() const
Gets any non-typed advanced options set by modifications.
Definition: unit.cpp:1721
Standard logging facilities (interface).
int current_side() const
Returns the number of the side whose turn it is.
Class to encapsulate fog/shroud clearing and the resultant sighted events.
Definition: vision.hpp:57
game_events::wml_event_pump & pump()
Definition: manager.cpp:229
static lg::log_domain log_engine("engine")
PHASE phase() const
Definition: game_data.hpp:76
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
bool valid() const
Definition: map.hpp:276
const advances_to_t & advances_to() const
Gets the possible types this unit can advance to on level-up.
Definition: unit.hpp:203
std::vector< int > get_sides_not_seeing(const unit &target)
Returns the sides that cannot currently see target.
Definition: vision.cpp:595
Display units performing various actions: moving, attacking, and dying.
#define LOG_DP
Definition: advancement.cpp:52
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:622
static game_display * get_singleton()
#define LOG_NG
Definition: advancement.cpp:44