The Battle for Wesnoth  1.15.0-dev
udisplay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 /** @file */
16 
17 #include "units/udisplay.hpp"
18 
19 #include "fake_unit_manager.hpp"
20 #include "fake_unit_ptr.hpp"
21 #include "game_board.hpp"
22 #include "game_display.hpp"
23 #include "preferences/game.hpp"
24 #include "log.hpp"
25 #include "mouse_events.hpp"
26 #include "resources.hpp"
27 #include "color.hpp"
28 #include "sound.hpp"
29 #include "terrain/filter.hpp"
30 #include "units/unit.hpp"
32 #include "units/filter.hpp"
33 #include "units/map.hpp"
34 
35 #define LOG_DP LOG_STREAM(info, display)
36 
37 
38 namespace unit_display
39 {
40 namespace
41 {
42 /**
43  * Returns a string whose first line is @a number, centered over a second line,
44  * which consists of @a text.
45  * If the number is 0, the first line is suppressed.
46  */
47 std::string number_and_text(int number, const std::string& text)
48 {
49  // Simple case.
50  if ( number == 0 )
51  return text;
52 
53  std::ostringstream result;
54 
55  if ( text.empty() )
56  result << number;
57  else
58  result << std::string((text.size()+1)/2, ' ') << number << '\n' << text;
59 
60  return result.str();
61 }
62 
63 
64 /**
65  * Animates a teleportation between hexes.
66  *
67  * @param a The starting hex.
68  * @param b The ending hex.
69  * @param temp_unit The unit to animate (historically, a temporary unit).
70  * @param disp The game display. Assumed neither locked nor faked.
71  */
72 void teleport_unit_between(const map_location& a, const map_location& b, unit& temp_unit, display& disp)
73 {
74  if ( disp.fogged(a) && disp.fogged(b) ) {
75  return;
76  }
77  const display_context& dc = disp.get_disp_context();
78  const team& viewing_team = dc.get_team(disp.viewing_side());
79 
80  const bool a_visible = temp_unit.is_visible_to_team(a, viewing_team, false);
81  const bool b_visible = temp_unit.is_visible_to_team(b, viewing_team, false);
82 
83  if ( a_visible ) { // teleport
84  disp.invalidate(a);
85  temp_unit.set_facing(a.get_relative_dir(b));
86  if ( b_visible )
87  disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
88  else
89  disp.scroll_to_tile(a, game_display::ONSCREEN, true, false);
90  unit_animator animator;
91  animator.add_animation(&temp_unit,"pre_teleport",a);
92  animator.start_animations();
93  animator.wait_for_end();
94  }
95 
96  if ( b_visible ) { // teleport
97  disp.invalidate(b);
98  temp_unit.set_facing(a.get_relative_dir(b));
99  if ( a_visible )
100  disp.scroll_to_tiles(b, a, game_display::ONSCREEN, true, 0.0, false);
101  else
102  disp.scroll_to_tile(b, game_display::ONSCREEN, true, false);
103  unit_animator animator;
104  animator.add_animation(&temp_unit,"post_teleport",b);
105  animator.start_animations();
106  animator.wait_for_end();
107  }
108 
109  temp_unit.anim_comp().set_standing();
110  disp.update_display();
111  events::pump();
112 }
113 
114 /**
115  * Animates a single step between hexes.
116  * This will return before the animation actually finishes, allowing other
117  * processing to occur during the animation.
118  *
119  * @param a The starting hex.
120  * @param b The ending hex.
121  * @param temp_unit The unit to animate (historically, a temporary unit).
122  * @param step_num The number of steps taken so far (used to pick an animation).
123  * @param step_left The number of steps remaining (used to pick an animation).
124  * @param animator The unit_animator to use. This is assumed clear when we start,
125  * but will likely not be clear when we return.
126  * @param disp The game display. Assumed neither locked nor faked.
127  * @returns The animation potential until this animation will finish.
128  * INT_MIN indicates that no animation is pending.
129  */
130 int move_unit_between(const map_location& a,
131  const map_location& b,
132  unit_ptr temp_unit,
133  unsigned int step_num,
134  unsigned int step_left,
135  unit_animator& animator,
136  display& disp)
137 {
138  if ( disp.fogged(a) && disp.fogged(b) ) {
139  return INT_MIN;
140  }
141 
142  temp_unit->set_location(a);
143  disp.invalidate(a);
144  temp_unit->set_facing(a.get_relative_dir(b));
145  animator.replace_anim_if_invalid(temp_unit.get(),"movement",a,b,step_num,
146  false,"",{0,0,0},unit_animation::hit_type::INVALID,nullptr,nullptr,step_left);
147  animator.start_animations();
148  animator.pause_animation();
149  disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
150  animator.restart_animation();
151 
152  // useless now, previous short draw() just did one
153  // new_animation_frame();
154 
155  int target_time = animator.get_animation_time_potential();
156  // target_time must be short to avoid jumpy move
157  // std::cout << "target time: " << target_time << "\n";
158  // we round it to the next multiple of 200 so that movement aligns to hex changes properly
159  target_time += 200;
160  target_time -= target_time%200;
161 
162  return target_time;
163 }
164 
165 bool do_not_show_anims(display* disp)
166 {
167 
168  return !disp || disp->video().update_locked() || disp->video().faked();
169 }
170 
171 } // end anon namespace
172 
173 /**
174  * The path must remain unchanged for the life of this object.
175  */
176 unit_mover::unit_mover(const std::vector<map_location>& path, bool animate, bool force_scroll) :
177  disp_(game_display::get_singleton()),
178  can_draw_(disp_ && !disp_->video().update_locked() &&
179  !disp_->video().faked() && path.size() > 1),
180  animate_(animate),
181  force_scroll_(force_scroll),
182  animator_(),
183  wait_until_(INT_MIN),
184  shown_unit_(),
185  path_(path),
186  current_(0),
187  temp_unit_ptr_(),
188  // Somewhat arbitrary default values.
189  was_hidden_(false),
190  is_enemy_(true)
191 {
192  // Some error conditions that indicate something has gone very wrong.
193  // (This class can handle these conditions, but someone wanted them
194  // to be assertions.)
195  assert(!path_.empty());
196  assert(disp_);
197 }
198 
199 
201 {
202  // Make sure a unit hidden for movement is unhidden.
204  // For safety, clear the animator before deleting the temp unit.
205  animator_.clear();
206 }
207 
208 
209 /**
210  * Makes the temporary unit used by this match the supplied unit.
211  * This is called when setting the initial unit, as well as replacing it with
212  * something new.
213  * When this finishes, the supplied unit is hidden, while the temporary unit
214  * is not hidden.
215  */
216 /* Note: Hide the unit in its current location; do not actually remove it.
217  * Otherwise the status displays will be wrong during the movement.
218  */
220 {
221  if ( disp_ == nullptr )
222  // No point in creating a temp unit with no way to display it.
223  return;
224 
225  // Save the hidden state of the unit.
226  was_hidden_ = u->get_hidden();
227 
228  // Make our temporary unit mostly match u...
230 
231  // ... but keep the temporary unhidden and hide the original.
232  temp_unit_ptr_->set_hidden(false);
233  u->set_hidden(true);
234 
235  // Update cached data.
236  is_enemy_ = resources::gameboard->get_team(u->side()).is_enemy(disp_->viewing_side());
237 }
238 
239 
240 /**
241  * Switches the display back to *shown_unit_ after animating.
242  * This uses temp_unit_ptr_, so (in the destructor) call this before deleting
243  * temp_unit_ptr_.
244  */
246 {
247  if ( shown_unit_ ) {
248  // Switch the display back to the real unit.
249  shown_unit_->set_hidden(was_hidden_);
250  temp_unit_ptr_->set_hidden(true);
251  shown_unit_.reset();
252  }
253 }
254 
255 
256 /**
257  * Initiates the display of movement for the supplied unit.
258  * This should be called before attempting to display moving to a new hex.
259  */
261 {
262  // Nothing to do here if there is nothing to animate.
263  if ( !can_draw_ )
264  return;
265  // If no animation then hide unit until end of movement
266  if ( !animate_ ) {
267  was_hidden_ = u->get_hidden();
268  u->set_hidden(true);
269  return;
270  }
271 
272  // This normally does nothing, but just in case...
273  wait_for_anims();
274 
275  // Visually replace the original unit with the temporary.
276  // (Original unit is left on the map, so the unit count is correct.)
278 
279  // Initialize our temporary unit for the move.
280  temp_unit_ptr_->set_location(path_[0]);
281  temp_unit_ptr_->set_facing(path_[0].get_relative_dir(path_[1]));
282  temp_unit_ptr_->anim_comp().set_standing(false);
283  disp_->invalidate(path_[0]);
284 
285  // If the unit can be seen here by the viewing side:
286  if(!is_enemy_ || !temp_unit_ptr_->invisible(path_[0])) {
287  // Scroll to the path, but only if it fully fits on screen.
288  // If it does not fit we might be able to do a better scroll later.
289  disp_->scroll_to_tiles(path_, game_display::ONSCREEN, true, true, 0.0, false);
290  }
291 
292  // extra immobile movement animation for take-off
293  animator_.add_animation(temp_unit_ptr_.get(), "pre_movement", path_[0], path_[1]);
296  animator_.clear();
297 
298  // Switch the display back to the real unit.
299  u->set_facing(temp_unit_ptr_->facing());
300  u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
301  u->set_hidden(was_hidden_);
302  temp_unit_ptr_->set_hidden(true);
303 }
304 
305 
306 /**
307  * Visually moves a unit from the last hex we drew to the one specified by
308  * @a path_index. If @a path_index points to an earlier hex, we do nothing.
309  * The moving unit will only be updated if update is set to true; otherwise,
310  * the provided unit is merely hidden during the movement and re-shown after.
311  * (Not updating the unit can produce smoother animations in some cases.)
312  * If @a wait is set to false, this returns without waiting for the final
313  * animation to finish. Call wait_for_anims() to explicitly get this final
314  * wait (another call to proceed_to() or finish() will implicitly wait). The
315  * unit must remain valid until the wait is finished.
316  */
317 void unit_mover::proceed_to(unit_ptr u, std::size_t path_index, bool update, bool wait)
318 {
319  // Nothing to do here if animations cannot be shown.
320  if ( !can_draw_ || !animate_ )
321  return;
322 
323  // Handle pending visibility issues before introducing new ones.
324  wait_for_anims();
325 
326  if ( update || !temp_unit_ptr_ )
327  // Replace the temp unit (which also hides u and shows our temporary).
329  else
330  {
331  // Just switch the display from the real unit to our fake one.
332  temp_unit_ptr_->set_hidden(false);
333  u->set_hidden(true);
334  }
335 
336  // Safety check.
337  path_index = std::min(path_index, path_.size()-1);
338 
339  for ( ; current_ < path_index; ++current_ ) {
340  // It is possible for path_[current_] and path_[current_+1] not to be adjacent.
341  // When that is the case, and the unit is invisible at path_[current_], we shouldn't
342  // scroll to that hex.
343  std::vector<map_location> locs;
344  if (!temp_unit_ptr_->invisible(path_[current_]))
345  locs.push_back(path_[current_]);
346  if (!temp_unit_ptr_->invisible(path_[current_+1]))
347  locs.push_back(path_[current_+1]);
348  // If the unit can be seen by the viewing side while making this step:
349  if ( !is_enemy_ || !locs.empty() )
350  {
351  // Wait for the previous step to complete before drawing the next one.
352  wait_for_anims();
353 
354  if ( !disp_->tile_fully_on_screen(path_[current_]) ||
355  !disp_->tile_fully_on_screen(path_[current_+1]))
356  {
357  // prevent the unit from disappearing if we scroll here with i == 0
358  temp_unit_ptr_->set_location(path_[current_]);
359  disp_->invalidate(path_[current_]);
360  // scroll in as much of the remaining path as possible
361  if ( temp_unit_ptr_->anim_comp().get_animation() )
362  temp_unit_ptr_->anim_comp().get_animation()->pause_animation();
364  true, false, 0.0, force_scroll_);
365  if ( temp_unit_ptr_->anim_comp().get_animation() )
366  temp_unit_ptr_->anim_comp().get_animation()->restart_animation();
367  }
368 
369  if ( tiles_adjacent(path_[current_], path_[current_+1]) )
370  wait_until_ =
371  move_unit_between(path_[current_], path_[current_+1],
373  path_.size() - (current_+2), animator_,
374  *disp_);
375  else if ( path_[current_] != path_[current_+1] )
376  teleport_unit_between(path_[current_], path_[current_+1],
377  *temp_unit_ptr_, *disp_);
378  }
379  }
380 
381  // Update the unit's facing.
382  u->set_facing(temp_unit_ptr_->facing());
383  u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
384  // Remember the unit to unhide when the animation finishes.
385  shown_unit_ = u;
386  if ( wait )
387  wait_for_anims();
388 }
389 
390 
391 /**
392  * Waits for the final animation of the most recent proceed_to() to finish.
393  * It is not necessary to call this unless you want to wait before the next
394  * call to proceed_to() or finish().
395  */
397 {
398  if ( wait_until_ == INT_MAX )
399  // Wait for end (not currently used, but still supported).
401  else if ( wait_until_ != INT_MIN ) {
402  // Wait until the specified time (used for normal movement).
404  // debug code, see unit_frame::redraw()
405  // std::cout << " end\n";
406  /// @todo For wesnoth 1.14+: check if efficient for redrawing?
407  /// Check with large animated units too make sure artifacts are
408  /// not left on screen after unit movement in particular.
409  if ( disp_ ) { // Should always be true if we get here.
410  // Invalidate the hexes around the move that prompted this wait.
412  get_adjacent_tiles(path_[current_-1], arr.data());
413  for ( unsigned i = 0; i < arr.size(); ++i )
414  disp_->invalidate(arr[i]);
415  get_adjacent_tiles(path_[current_], arr.data());
416  for ( unsigned i = 0; i < arr.size(); ++i )
417  disp_->invalidate(arr[i]);
418  }
419  }
420 
421  // Reset data.
422  wait_until_ = INT_MIN;
423  animator_.clear();
424 
426 }
427 
428 
429 /**
430  * Finishes the display of movement for the supplied unit.
431  * If called before showing the unit reach the end of the path, it will be
432  * assumed that the movement ended early.
433  * If @a dir is not supplied, the final direction will be determined by (the
434  * last two traversed hexes of) the path.
435  */
437 {
438  // Nothing to do here if the display is not valid.
439  if ( !can_draw_ ) {
440  // Make sure to reset the unit's animation to deal with a quirk in the
441  // action engine where it leaves it to us to reenable bars even if the
442  // display is initially locked.
443  u->anim_comp().set_standing(true);
444  return;
445  }
446 
447  const map_location & end_loc = path_[current_];
448  const map_location::DIRECTION final_dir = current_ == 0 ?
449  path_[0].get_relative_dir(path_[1]) :
450  path_[current_-1].get_relative_dir(end_loc);
451 
452  if ( animate_ )
453  {
454  wait_for_anims(); // In case proceed_to() did not wait for the last animation.
455 
456  // Make sure the displayed unit is correct.
458  temp_unit_ptr_->set_location(end_loc);
459  temp_unit_ptr_->set_facing(final_dir);
460 
461  // Animation
462  animator_.add_animation(temp_unit_ptr_.get(), "post_movement", end_loc);
465  animator_.clear();
466 
467  // Switch the display back to the real unit.
468  u->set_hidden(was_hidden_);
469  temp_unit_ptr_->set_hidden(true);
470 
472  mousehandler->invalidate_reachmap();
473  }
474  }
475  else
476  {
477  // Show the unit at end of skipped animation
478  u->set_hidden(was_hidden_);
479  }
480 
481  // Facing gets set even when not animating.
482  u->set_facing(dir == map_location::NDIRECTIONS ? final_dir : dir);
483  u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
484 
485  // Redraw path ends (even if not animating).
486  disp_->invalidate(path_.front());
487  disp_->invalidate(end_loc);
488 }
489 
490 
491 /**
492  * Display a unit moving along a given path.
493  *
494  * @param path The path to traverse.
495  * @param u The unit to show being moved. Its facing will be updated,
496  * but not its position.
497  * @param animate If set to false, only side-effects of move are applied
498  * (correct unit facing, path hexes redrawing).
499  * @param dir Unit will be set facing this direction after move.
500  * If nothing passed, direction will be set based on path.
501  */
502 /* Note: Hide the unit in its current location,
503  * but don't actually remove it until the move is done,
504  * so that while the unit is moving status etc.
505  * will still display the correct number of units.
506  */
507 void move_unit(const std::vector<map_location>& path, unit_ptr u,
508  bool animate, map_location::DIRECTION dir,
509  bool force_scroll)
510 {
511  unit_mover mover(path, animate, force_scroll);
512 
513  mover.start(u);
514  mover.proceed_to(u, path.size());
515  mover.finish(u, dir);
516 }
517 
518 
519 void reset_helpers(const unit *attacker,const unit *defender);
520 
521 void unit_draw_weapon(const map_location& loc, unit& attacker,
522  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& defender_loc,unit* defender)
523 {
525  if(do_not_show_anims(disp) || disp->fogged(loc) || !preferences::show_combat()) {
526  return;
527  }
528  unit_animator animator;
529  attacker.set_facing(loc.get_relative_dir(defender_loc));
530  defender->set_facing(defender_loc.get_relative_dir(loc));
531  animator.add_animation(&attacker,"draw_weapon",loc,defender_loc,0,false,"",{0,0,0},unit_animation::hit_type::HIT,attack,secondary_attack,0);
532  animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,false,"",{0,0,0},unit_animation::hit_type::MISS,secondary_attack,attack,0);
533  animator.start_animations();
534  animator.wait_for_end();
535 
536 }
537 
538 
539 void unit_sheath_weapon(const map_location& primary_loc, unit* primary_unit,
540  const_attack_ptr primary_attack,const_attack_ptr secondary_attack, const map_location& secondary_loc,unit* secondary_unit)
541 {
543  if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !preferences::show_combat()) {
544  return;
545  }
546  unit_animator animator;
547  if(primary_unit) {
548  animator.add_animation(primary_unit,"sheath_weapon",primary_loc,secondary_loc,0,false,"",{0,0,0},unit_animation::hit_type::INVALID,primary_attack,secondary_attack,0);
549  }
550  if(secondary_unit) {
551  animator.add_animation(secondary_unit,"sheath_weapon",secondary_loc,primary_loc,0,false,"",{0,0,0},unit_animation::hit_type::INVALID,secondary_attack,primary_attack,0);
552  }
553 
554  if(primary_unit || secondary_unit) {
555  animator.start_animations();
556  animator.wait_for_end();
557  }
558  if(primary_unit) {
559  primary_unit->anim_comp().set_standing();
560  }
561  if(secondary_unit) {
562  secondary_unit->anim_comp().set_standing();
563  }
564  reset_helpers(primary_unit,secondary_unit);
565 
566 }
567 
568 
569 void unit_die(const map_location& loc, unit& loser,
570  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& winner_loc,unit* winner)
571 {
573  if(do_not_show_anims(disp) || disp->fogged(loc) || !preferences::show_combat()) {
574  return;
575  }
576  unit_animator animator;
577  // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
578  animator.add_animation(&loser,"death",loc,winner_loc,0,false,"",{0,0,0},unit_animation::hit_type::KILL,attack,secondary_attack,0);
579  // but show the bars of the winner (avoid blinking and show its xp gain)
580  animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
581  unit_animation::hit_type::KILL,secondary_attack,attack,0);
582  animator.start_animations();
583  animator.wait_for_end();
584 
585  reset_helpers(winner, &loser);
586 
588  mousehandler->invalidate_reachmap();
589  }
590 }
591 
592 
593 void unit_attack(display * disp, game_board & board,
594  const map_location& a, const map_location& b, int damage,
595  const attack_type& attack, const_attack_ptr secondary_attack,
596  int swing,const std::string& hit_text,int drain_amount,const std::string& att_text, const std::vector<std::string>* extra_hit_sounds)
597 {
598  if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !preferences::show_combat()) {
599  return;
600  }
601  //const unit_map& units = disp->get_units();
603 
604  // scroll such that there is at least half a hex spacing around fighters
605  disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
606 
607  log_scope("unit_attack");
608 
609  const unit_map::const_iterator att = board.units().find(a);
610  assert(att.valid());
611  const unit& attacker = *att;
612 
613  const unit_map::iterator def = board.find_unit(b);
614  assert(def.valid());
615  unit &defender = *def;
616  int def_hitpoints = defender.hitpoints();
617 
618  att->set_facing(a.get_relative_dir(b));
619  def->set_facing(b.get_relative_dir(a));
620  defender.set_facing(b.get_relative_dir(a));
621 
622  std::string text = number_and_text(damage, hit_text);
623  std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
624 
625  unit_animation::hit_type hit_type;
626  if(damage >= defender.hitpoints()) {
627  hit_type = unit_animation::hit_type::KILL;
628  } else if(damage > 0) {
629  hit_type = unit_animation::hit_type::HIT;
630  }else {
631  hit_type = unit_animation::hit_type::MISS;
632  }
633 
634  unit_animator animator;
635 
636  animator.add_animation(&attacker, "attack", att->get_location(), def->get_location(), damage, true, text_2,
637  (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, attack.shared_from_this(),
638  secondary_attack, swing);
639 
640  // note that we take an anim from the real unit, we'll use it later
641  const unit_animation* defender_anim = def->anim_comp().choose_animation(*disp, def->get_location(), "defend",
642  att->get_location(), damage, hit_type, attack.shared_from_this(), secondary_attack, swing);
643 
644  animator.add_animation(&defender, defender_anim, def->get_location(), true, text, {255, 0, 0});
645 
646  for(const unit_ability& ability : attacker.get_abilities("leadership")) {
647  if(ability.second == a) {
648  continue;
649  }
650 
651  if(ability.second == b) {
652  continue;
653  }
654 
655  unit_map::const_iterator leader = board.units().find(ability.second);
656  assert(leader.valid());
657  leader->set_facing(ability.second.get_relative_dir(a));
658  animator.add_animation(&*leader, "leading", ability.second,
659  att->get_location(), damage, true, "", {0,0,0},
660  hit_type, attack.shared_from_this(), secondary_attack, swing);
661  }
662 
663  for(const unit_ability& ability : defender.get_abilities("resistance")) {
664  if(ability.second == a) {
665  continue;
666  }
667 
668  if(ability.second == b) {
669  continue;
670  }
671 
672  unit_map::const_iterator helper = board.units().find(ability.second);
673  assert(helper.valid());
674  helper->set_facing(ability.second.get_relative_dir(b));
675  animator.add_animation(&*helper, "resistance", ability.second,
676  def->get_location(), damage, true, "", {0,0,0},
677  hit_type, attack.shared_from_this(), secondary_attack, swing);
678  }
679 
680 
681  animator.start_animations();
682  animator.wait_until(0);
683  int damage_left = damage;
684  bool extra_hit_sounds_played = false;
685  while(damage_left > 0 && !animator.would_end()) {
686  if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
687  for (std::string hit_sound : *extra_hit_sounds) {
688  sound::play_sound(hit_sound);
689  }
690  extra_hit_sounds_played = true;
691  }
692 
693  int step_left = (animator.get_end_time() - animator.get_animation_time() )/50;
694  if(step_left < 1) step_left = 1;
695  int removed_hp = damage_left/step_left ;
696  if(removed_hp < 1) removed_hp = 1;
697  defender.take_hit(removed_hp);
698  damage_left -= removed_hp;
699  animator.wait_until(animator.get_animation_time_potential() +50);
700  }
701  animator.wait_for_end();
702  // pass the animation back to the real unit
703  def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
704  reset_helpers(&*att, &*def);
705  def->set_hitpoints(def_hitpoints);
706 }
707 
708 // private helper function, set all helpers to default position
709 void reset_helpers(const unit *attacker,const unit *defender)
710 {
712  const unit_map& units = disp->get_units();
713  if(attacker) {
714  for(const unit_ability& ability : attacker->get_abilities("leadership")) {
715  unit_map::const_iterator leader = units.find(ability.second);
716  assert(leader != units.end());
717  leader->anim_comp().set_standing();
718  }
719  }
720 
721  if(defender) {
722  for(const unit_ability& ability : defender->get_abilities("resistance")) {
723  unit_map::const_iterator helper = units.find(ability.second);
724  assert(helper != units.end());
725  helper->anim_comp().set_standing();
726  }
727  }
728 }
729 
730 void unit_recruited(const map_location& loc,const map_location& leader_loc)
731 {
733  if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
734  return;
735  }
736 
737  const display_context& dc = disp->get_disp_context();
738  const team& viewing_team = dc.get_team(disp->viewing_side());
739 
740  unit_map::const_iterator u = disp->get_units().find(loc);
741  if(u == disp->get_units().end()) return;
742  const bool unit_visible = u->is_visible_to_team(viewing_team, false);
743 
744  unit_map::const_iterator leader = disp->get_units().find(leader_loc); // may be null_location
745  const bool leader_visible = (leader != disp->get_units().end()) && leader->is_visible_to_team(viewing_team, false);
746 
747  u->set_hidden(true);
748 
749  unit_animator animator;
750 
751  if (leader_visible && unit_visible) {
752  disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
753  } else if (leader_visible) {
754  disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
755  } else if (unit_visible) {
756  disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
757  } else {
758  return;
759  }
760  if (leader != disp->get_units().end()) {
761  leader->set_facing(leader_loc.get_relative_dir(loc));
762  if (leader_visible) {
763  animator.add_animation(&*leader, "recruiting", leader_loc, loc, 0, true);
764  }
765  }
766 
767  disp->draw();
768  u->set_hidden(false);
769  animator.add_animation(&*u, "recruited", loc, leader_loc);
770  animator.start_animations();
771  animator.wait_for_end();
772  animator.set_all_standing();
773  if (loc==disp->mouseover_hex()) disp->invalidate_unit();
774 }
775 
776 void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
777  const std::string & extra_text)
778 {
780  const map_location& healed_loc = healed.get_location();
781 
782  if(do_not_show_anims(disp) || disp->fogged(healed_loc)) {
783  return;
784  }
785 
786  // This is all the pretty stuff.
787  disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
788  disp->display_unit_hex(healed_loc);
789  unit_animator animator;
790 
791  for (unit *h : healers) {
792  h->set_facing(h->get_location().get_relative_dir(healed_loc));
793  animator.add_animation(h, "healing", h->get_location(),
794  healed_loc, healing);
795  }
796 
797  if (healing < 0) {
798  animator.add_animation(&healed, "poisoned", healed_loc,
799  map_location::null_location(), -healing, false,
800  number_and_text(-healing, extra_text),
801  {255,0,0});
802  } else if ( healing > 0 ) {
803  animator.add_animation(&healed, "healed", healed_loc,
804  map_location::null_location(), healing, false,
805  number_and_text(healing, extra_text),
806  {0,255,0});
807  } else {
808  animator.add_animation(&healed, "healed", healed_loc,
809  map_location::null_location(), 0, false,
810  extra_text, {0,255,0});
811  }
812  animator.start_animations();
813  animator.wait_for_end();
814  animator.set_all_standing();
815 }
816 
817 } // end unit_display namespace
const map_location & mouseover_hex() const
Definition: display.hpp:284
void wait_for_end() const
Definition: animation.cpp:1442
Game board class.
Definition: game_board.hpp:50
virtual void select_hex(map_location hex)
Definition: display.cpp:1617
unit_iterator end()
Definition: map.hpp:415
bool update_locked() const
Whether the screen has been &#39;locked&#39; or not.
Definition: video.cpp:357
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:87
const team & get_team(int side) const
void start_animations()
Definition: animation.cpp:1383
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:517
virtual const unit_map & units() const override
Definition: game_board.hpp:114
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:225
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3008
game_display *const disp_
Definition: udisplay.hpp:62
void unit_draw_weapon(const map_location &loc, unit &attacker, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &defender_loc, unit *defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
Definition: udisplay.cpp:521
This class represents a single unit of a specific type.
Definition: unit.hpp:99
internal_ptr get_unit_ptr()
Get a copy of the internal unit pointer.
bool would_end() const
Definition: animation.cpp:1407
#define a
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
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
const unit_map & get_units() const
Definition: display.hpp:120
int viewing_side() const
Definition: display.hpp:102
unit_mover(const unit_mover &)=delete
#define h
virtual void draw()
Draws invalidated items.
Definition: display.cpp:2464
void set_facing(map_location::DIRECTION dir) const
The this unit&#39;s facing.
Definition: unit.cpp:1518
void update_display()
Copy the backbuffer to the framebuffer.
Definition: display.cpp:1360
int get_animation_time_potential() const
Definition: animation.cpp:1464
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
Definition: sound.cpp:980
Contains a number of free functions which display units.
Definition: udisplay.cpp:38
void wait_until(int animation_time) const
Definition: animation.cpp:1417
#define b
unit_ptr shown_unit_
The animation potential to wait until. INT_MIN for no wait; INT_MAX to wait for end.
Definition: udisplay.hpp:68
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
team & get_team(int i)
Definition: game_board.hpp:104
static mouse_handler * get_singleton()
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
void finish(unit_ptr u, map_location::DIRECTION dir=map_location::NDIRECTIONS)
Finishes the display of movement for the supplied unit.
Definition: udisplay.cpp:436
std::pair< const config *, map_location > unit_ability
Definition: unit.hpp:48
void restart_animation()
Definition: animation.cpp:1490
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:2091
const bool force_scroll_
Definition: udisplay.hpp:65
unit_animator animator_
Definition: udisplay.hpp:66
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition: udisplay.cpp:245
game_board * gameboard
Definition: resources.cpp:20
void replace_anim_if_invalid(const unit *animated_unit, const std::string &event, const map_location &src=map_location::null_location(), const map_location &dst=map_location::null_location(), const int value=0, bool with_bars=false, const std::string &text="", const color_t text_color={0, 0, 0}, const unit_animation::hit_type hit_type=unit_animation::hit_type::INVALID, const_attack_ptr attack=nullptr, const_attack_ptr second_attack=nullptr, int value2=0)
Definition: animation.cpp:1348
void unit_die(const map_location &loc, unit &loser, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &winner_loc, unit *winner)
Show a unit fading out.
Definition: udisplay.cpp:569
fake_unit_manager * fake_units
Definition: resources.cpp:30
std::string path
Definition: game_config.cpp:39
A class to encapsulate the steps of drawing a unit&#39;s move.
Definition: udisplay.hpp:44
void unit_healing(unit &healed, const std::vector< unit *> &healers, int healing, const std::string &extra_text)
This will use a poisoning anim if healing<0.
Definition: udisplay.cpp:776
const std::vector< map_location > & path_
The unit to be (re-)shown after an animation finishes.
Definition: udisplay.hpp:69
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:396
bool show_combat()
Definition: game.cpp:448
std::array< map_location, 6 > adjacent_loc_array_t
Definition: location.hpp:170
void pump()
Definition: events.cpp:425
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:743
Encapsulates the map of the game.
Definition: location.hpp:42
unit_iterator find(std::size_t id)
Definition: map.cpp:311
bool faked() const
Definition: video.hpp:60
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:557
unit * get()
Get a raw pointer to the underlying unit.
void replace_temporary(unit_ptr u)
Makes the temporary unit used by this match the supplied unit.
Definition: udisplay.cpp:219
std::size_t i
Definition: function.cpp:933
unit_animation_component & anim_comp() const
Definition: unit.hpp:1371
int get_end_time() const
Definition: animation.cpp:1469
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:2187
void unit_sheath_weapon(const map_location &primary_loc, unit *primary_unit, const_attack_ptr primary_attack, const_attack_ptr secondary_attack, const map_location &secondary_loc, unit *secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation...
Definition: udisplay.cpp:539
void scroll_to_tiles(map_location loc1, map_location loc2, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, double add_spacing=0.0, bool force=true)
Scroll such that location loc1 is on-screen.
Definition: display.cpp:2199
void pause_animation()
Definition: animation.cpp:1481
void proceed_to(unit_ptr u, std::size_t path_index, bool update=false, bool wait=true)
Visually moves a unit from the last hex we drew to the one specified by path_index.
Definition: udisplay.cpp:317
unit_map::iterator find_unit(const map_location &loc)
Definition: game_board.hpp:174
void start(unit_ptr u)
Initiates the display of movement for the supplied unit.
Definition: udisplay.cpp:260
#define log_scope(description)
Definition: log.hpp:186
const display_context & get_disp_context() const
Definition: display.hpp:167
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:44
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
Gets the unit&#39;s active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:181
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2361
void reset_helpers(const unit *attacker, const unit *defender)
Definition: udisplay.cpp:709
int get_animation_time() const
Definition: animation.cpp:1459
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
void set_all_standing()
Definition: animation.cpp:1499
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1184
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition: udisplay.cpp:730
Standard logging facilities (interface).
fake_unit_ptr temp_unit_ptr_
Definition: udisplay.hpp:71
CVideo & video()
Gets the underlying screen object.
Definition: display.hpp:195
static const map_location & null_location()
Definition: location.hpp:85
Container associating units to locations.
Definition: map.hpp:99
void set_standing(bool with_bars=true)
Sets the animation state to standing.
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const_attack_ptr secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds)
Make the unit on tile &#39;a&#39; attack the unit on tile &#39;b&#39;.
Definition: udisplay.cpp:593
bool valid() const
Definition: map.hpp:276
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:37
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
Holds a temporary unit that can be drawn on the map without being placed in the unit_map.
Display units performing various actions: moving, attacking, and dying.
void move_unit(const std::vector< map_location > &path, unit_ptr u, bool animate, map_location::DIRECTION dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:507
static game_display * get_singleton()