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