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