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