The Battle for Wesnoth  1.17.0-dev
udisplay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "units/udisplay.hpp"
17 
18 #include "fake_unit_manager.hpp"
19 #include "fake_unit_ptr.hpp"
20 #include "game_board.hpp"
21 #include "game_display.hpp"
22 #include "preferences/game.hpp"
23 #include "log.hpp"
24 #include "mouse_events.hpp"
25 #include "resources.hpp"
26 #include "color.hpp"
27 #include "sound.hpp"
28 #include "terrain/filter.hpp"
29 #include "units/unit.hpp"
31 #include "units/filter.hpp"
32 #include "units/map.hpp"
33 #include "utils/scope_exit.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.shared_from_this(),"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.shared_from_this(),"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,"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_unit_ptr(), "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.
413  for(const map_location& adj : get_adjacent_tiles(path_[current_ - 1])) {
414  disp_->invalidate(adj);
415  }
416 
417  for(const map_location& adj : get_adjacent_tiles(path_[current_])) {
418  disp_->invalidate(adj);
419  }
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_unit_ptr(), "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  * @param force_scroll
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_ptr 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.shared_from_this(),"draw_weapon",loc,defender_loc,0,true,"",{0,0,0},unit_animation::hit_type::HIT,attack,secondary_attack,0);
535  if(defender) {
536  animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},unit_animation::hit_type::MISS,secondary_attack,attack,0);
537  }
538  animator.start_animations();
539  animator.wait_for_end();
540 
541 }
542 
543 
544 void unit_sheath_weapon(const map_location& primary_loc, unit_ptr primary_unit,
545  const_attack_ptr primary_attack,const_attack_ptr secondary_attack, const map_location& secondary_loc,unit_ptr secondary_unit)
546 {
548  if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !preferences::show_combat()) {
549  return;
550  }
551  unit_animator animator;
552  if(primary_unit) {
553  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);
554  }
555  if(secondary_unit) {
556  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);
557  }
558 
559  if(primary_unit || secondary_unit) {
560  animator.start_animations();
561  animator.wait_for_end();
562  }
563  if(primary_unit) {
564  primary_unit->anim_comp().set_standing();
565  }
566  if(secondary_unit) {
567  secondary_unit->anim_comp().set_standing();
568  }
569  reset_helpers(primary_unit.get(),secondary_unit.get());
570 
571 }
572 
573 
574 void unit_die(const map_location& loc, unit& loser,
575  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& winner_loc, unit_ptr winner)
576 {
578  if(do_not_show_anims(disp) || disp->fogged(loc) || !preferences::show_combat()) {
579  return;
580  }
581  unit_animator animator;
582  // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
583  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);
584  // but show the bars of the winner (avoid blinking and show its xp gain)
585  if(winner) {
586  animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
587  unit_animation::hit_type::KILL,secondary_attack,attack,0);
588  }
589  animator.start_animations();
590  animator.wait_for_end();
591 
592  reset_helpers(winner.get(), &loser);
593 
595  mousehandler->invalidate_reachmap();
596  }
597 }
598 
599 
600 void unit_attack(display * disp, game_board & board,
601  const map_location& a, const map_location& b, int damage,
602  const attack_type& attack, const_attack_ptr secondary_attack,
603  int swing,const std::string& hit_text,int drain_amount,const std::string& att_text, const std::vector<std::string>* extra_hit_sounds,
604  bool attacking)
605 {
606  if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !preferences::show_combat()) {
607  return;
608  }
609  //const unit_map& units = disp->get_units();
611 
612  // scroll such that there is at least half a hex spacing around fighters
613  disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
614 
615  log_scope("unit_attack");
616 
617  const unit_map::const_iterator att = board.units().find(a);
618  assert(att.valid());
619  const unit& attacker = *att;
620 
621  const unit_map::iterator def = board.find_unit(b);
622  assert(def.valid());
623  unit &defender = *def;
624  int def_hitpoints = defender.hitpoints();
625  const_attack_ptr weapon = attack.shared_from_this();
626  auto ctx = weapon->specials_context(attacker.shared_from_this(), defender.shared_from_this(), a, b, attacking, secondary_attack);
627  std::optional<decltype(ctx)> opp_ctx;
628 
629  if(secondary_attack) {
630  opp_ctx.emplace(secondary_attack->specials_context(defender.shared_from_this(), attacker.shared_from_this(), b, a, !attacking, weapon));
631  }
632 
633  att->set_facing(a.get_relative_dir(b));
634  def->set_facing(b.get_relative_dir(a));
635  defender.set_facing(b.get_relative_dir(a));
636 
637  std::string text = number_and_text(damage, hit_text);
638  std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
639 
640  unit_animation::hit_type hit_type;
641  if(damage >= defender.hitpoints()) {
642  hit_type = unit_animation::hit_type::KILL;
643  } else if(damage > 0) {
644  hit_type = unit_animation::hit_type::HIT;
645  }else {
646  hit_type = unit_animation::hit_type::MISS;
647  }
648 
649  unit_animator animator;
650 
651  animator.add_animation(attacker.shared_from_this(), "attack", att->get_location(), def->get_location(), damage, true, text_2,
652  (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, weapon,
653  secondary_attack, swing);
654 
655  // note that we take an anim from the real unit, we'll use it later
656  const unit_animation* defender_anim = def->anim_comp().choose_animation(*disp, def->get_location(), "defend",
657  att->get_location(), damage, hit_type, weapon, secondary_attack, swing);
658 
659  animator.add_animation(defender.shared_from_this(), defender_anim, def->get_location(), true, text, {255, 0, 0});
660 
661  unit_ability_list leadership_list = attacker.get_abilities_weapons("leadership", weapon, secondary_attack);
662  unit_ability_list resistance_list = defender.get_abilities_weapons("resistance", secondary_attack, weapon);
663  for(const unit_ability& ability : leadership_list) {
664  if(ability.teacher_loc == a) {
665  continue;
666  }
667 
668  if(ability.teacher_loc == b) {
669  continue;
670  }
671 
672  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
673  assert(leader.valid());
674  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
675  animator.add_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
676  att->get_location(), damage, true, "", {0,0,0},
677  hit_type, weapon, secondary_attack, swing);
678  }
679 
680  for(const unit_ability& ability : resistance_list) {
681  if(ability.teacher_loc == a) {
682  continue;
683  }
684 
685  if(ability.teacher_loc == b) {
686  continue;
687  }
688 
689  unit_map::const_iterator helper = board.units().find(ability.teacher_loc);
690  assert(helper.valid());
691  helper->set_facing(ability.teacher_loc.get_relative_dir(b));
692  animator.add_animation(helper.get_shared_ptr(), "resistance", ability.teacher_loc,
693  def->get_location(), damage, true, "", {0,0,0},
694  hit_type, weapon, secondary_attack, swing);
695  }
696 
697  unit_ability_list abilities = att->get_location();
698  for(auto& special : attacker.checking_tags()) {
699  abilities.append(weapon->get_weapon_ability(special));
700  }
701 
702  for(const unit_ability& ability : abilities) {
703  if(ability.teacher_loc == a) {
704  continue;
705  }
706 
707  if(ability.teacher_loc == b) {
708  continue;
709  }
710 
711  bool leading_playable = false;
712  bool helping_playable = false;
713  for(const unit_ability& leader_list : leadership_list) {
714  if(ability.teacher_loc == leader_list.teacher_loc) {
715  leading_playable = true;
716  break;
717  }
718  }
719 
720  for(const unit_ability& helper_list : resistance_list) {
721  if(ability.teacher_loc == helper_list.teacher_loc) {
722  helping_playable = true;
723  break;
724  }
725  }
726 
727  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
728  assert(leader.valid());
729  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
730  if(animator.has_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
731  att->get_location(), damage, hit_type, weapon, secondary_attack, swing) && leading_playable){
732  continue;
733  }
734  if(animator.has_animation(leader.get_shared_ptr(), "resistance", ability.teacher_loc,
735  def->get_location(), damage, hit_type, weapon, secondary_attack, swing) && helping_playable){
736  continue;
737  }
738  animator.add_animation(leader.get_shared_ptr(), "teaching", ability.teacher_loc,
739  att->get_location(), damage, true, "", {0,0,0},
740  hit_type, weapon, secondary_attack, swing);
741  }
742 
743 
744  animator.start_animations();
745  animator.wait_until(0);
746  int damage_left = damage;
747  bool extra_hit_sounds_played = false;
748  while(damage_left > 0 && !animator.would_end()) {
749  if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
750  for (std::string hit_sound : *extra_hit_sounds) {
751  sound::play_sound(hit_sound);
752  }
753  extra_hit_sounds_played = true;
754  }
755 
756  int step_left = (animator.get_end_time() - animator.get_animation_time() )/50;
757  if(step_left < 1) step_left = 1;
758  int removed_hp = damage_left/step_left ;
759  if(removed_hp < 1) removed_hp = 1;
760  defender.take_hit(removed_hp);
761  damage_left -= removed_hp;
762  animator.wait_until(animator.get_animation_time_potential() +50);
763  }
764  animator.wait_for_end();
765  // pass the animation back to the real unit
766  def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
767  reset_helpers(&*att, &*def);
768  def->set_hitpoints(def_hitpoints);
769 }
770 
771 // private helper function, set all helpers to default position
772 void reset_helpers(const unit *attacker,const unit *defender)
773 {
775  const unit_map& units = disp->get_units();
776  if(attacker) {
777  unit_ability_list attacker_abilities = attacker->get_abilities("leadership");
778  for(auto& special : attacker->checking_tags()) {
779  attacker_abilities.append(attacker->get_abilities(special));
780  }
781  for(const unit_ability& ability : attacker_abilities) {
782  unit_map::const_iterator leader = units.find(ability.teacher_loc);
783  assert(leader != units.end());
784  leader->anim_comp().set_standing();
785  }
786  }
787 
788  if(defender) {
789  unit_ability_list defender_abilities = defender->get_abilities("resistance");
790  for(auto& special : defender->checking_tags()) {
791  defender_abilities.append(defender->get_abilities(special));
792  }
793  for(const unit_ability& ability : defender_abilities) {
794  unit_map::const_iterator helper = units.find(ability.teacher_loc);
795  assert(helper != units.end());
796  helper->anim_comp().set_standing();
797  }
798  }
799 }
800 
801 void unit_recruited(const map_location& loc,const map_location& leader_loc)
802 {
804  if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
805  return;
806  }
807 
808  const display_context& dc = disp->get_disp_context();
809  const team& viewing_team = dc.get_team(disp->viewing_side());
810 
811  unit_map::const_iterator u = disp->get_units().find(loc);
812  if(u == disp->get_units().end()) return;
813  const bool unit_visible = u->is_visible_to_team(viewing_team, false);
814 
815  unit_map::const_iterator leader = disp->get_units().find(leader_loc); // may be null_location
816  const bool leader_visible = (leader != disp->get_units().end()) && leader->is_visible_to_team(viewing_team, false);
817 
818  unit_animator animator;
819 
820  {
821  ON_SCOPE_EXIT(u) {
822  u->set_hidden(false);
823  };
824  u->set_hidden(true);
825 
826  if (leader_visible && unit_visible) {
827  disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
828  } else if (leader_visible) {
829  disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
830  } else if (unit_visible) {
831  disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
832  } else {
833  return;
834  }
835  if (leader != disp->get_units().end()) {
836  leader->set_facing(leader_loc.get_relative_dir(loc));
837  if (leader_visible) {
838  animator.add_animation(leader.get_shared_ptr(), "recruiting", leader_loc, loc, 0, true);
839  }
840  }
841 
842  disp->draw();
843  }
844  animator.add_animation(u.get_shared_ptr(), "recruited", loc, leader_loc);
845  animator.start_animations();
846  animator.wait_for_end();
847  animator.set_all_standing();
848  if (loc==disp->mouseover_hex()) disp->invalidate_unit();
849 }
850 
851 void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
852  const std::string & extra_text)
853 {
855  const map_location& healed_loc = healed.get_location();
856  const bool some_healer_is_unfogged =
857  (healers.end() != std::find_if_not(healers.begin(), healers.end(),
858  [&](unit* h) { return disp->fogged(h->get_location()); }));
859 
860  if(do_not_show_anims(disp) || (disp->fogged(healed_loc) && !some_healer_is_unfogged)) {
861  return;
862  }
863 
864  // This is all the pretty stuff.
865  disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
866  disp->display_unit_hex(healed_loc);
867  unit_animator animator;
868 
869  for (unit *h : healers) {
870  h->set_facing(h->get_location().get_relative_dir(healed_loc));
871  animator.add_animation(h->shared_from_this(), "healing", h->get_location(),
872  healed_loc, healing);
873  }
874 
875  if (healing < 0) {
876  animator.add_animation(healed.shared_from_this(), "poisoned", healed_loc,
877  map_location::null_location(), -healing, false,
878  number_and_text(-healing, extra_text),
879  {255,0,0});
880  } else if ( healing > 0 ) {
881  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
882  map_location::null_location(), healing, false,
883  number_and_text(healing, extra_text),
884  {0,255,0});
885  } else {
886  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
887  map_location::null_location(), 0, false,
888  extra_text, {0,255,0});
889  }
890  animator.start_animations();
891  animator.wait_for_end();
892  animator.set_all_standing();
893 }
894 
895 } // end unit_display namespace
const map_location & mouseover_hex() const
Definition: display.hpp:290
void wait_for_end() const
Definition: animation.cpp:1458
Game board class.
Definition: game_board.hpp:51
virtual void select_hex(map_location hex)
Definition: display.cpp:1620
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:524
unit_iterator end()
Definition: map.hpp:429
bool update_locked() const
Whether the screen has been &#39;locked&#39; or not.
Definition: video.cpp:328
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:92
const team & get_team(int side) const
void start_animations()
Definition: animation.cpp:1399
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:475
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:544
virtual const unit_map & units() const override
Definition: game_board.hpp:112
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:227
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3026
game_display *const disp_
Definition: udisplay.hpp:63
This class represents a single unit of a specific type.
Definition: unit.hpp:121
internal_ptr get_unit_ptr()
Get a copy of the internal unit pointer.
bool would_end() const
Definition: animation.cpp:1423
#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:1356
const unit_map & get_units() const
Definition: display.hpp:125
int viewing_side() const
Definition: display.hpp:107
unit_mover(const unit_mover &)=delete
#define h
virtual void draw()
Draws invalidated items.
Definition: display.cpp:2478
void set_facing(map_location::DIRECTION dir) const
The this unit&#39;s facing.
Definition: unit.cpp:1551
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
void update_display()
Copy the backbuffer to the framebuffer.
Definition: display.cpp:1364
int get_animation_time_potential() const
Definition: animation.cpp:1480
void play_sound(const std::string &files, channel_group group, unsigned int repeats)
Definition: sound.cpp:1021
Contains a number of free functions which display units.
Definition: udisplay.cpp:38
void wait_until(int animation_time) const
Definition: animation.cpp:1433
#define b
unit_ptr shown_unit_
The unit to be (re-)shown after an animation finishes.
Definition: udisplay.hpp:71
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:72
Data typedef for unit_ability_list.
Definition: unit.hpp:40
team & get_team(int i)
Definition: game_board.hpp:97
static mouse_handler * get_singleton()
const std::set< std::string > & checking_tags() const
Definition: unit.hpp:1714
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
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
void restart_animation()
Definition: animation.cpp:1506
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:2107
const bool force_scroll_
Definition: udisplay.hpp:66
unit_animator animator_
Definition: udisplay.hpp:67
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition: udisplay.cpp:247
game_board * gameboard
Definition: resources.cpp:21
fake_unit_manager * fake_units
Definition: resources.cpp:31
std::string path
Definition: game_config.cpp:39
A class to encapsulate the steps of drawing a unit&#39;s move.
Definition: udisplay.hpp:45
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:851
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:600
const std::vector< map_location > & path_
Definition: udisplay.hpp:72
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:460
int wait_until_
The animation potential to wait until.
Definition: udisplay.hpp:69
void pump()
Definition: events.cpp:473
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:745
Encapsulates the map of the game.
Definition: location.hpp:38
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:1327
unit_iterator find(std::size_t id)
Definition: map.cpp:310
bool faked() const
Definition: video.hpp:66
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:503
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:218
void replace_temporary(unit_ptr u)
Makes the temporary unit used by this match the supplied unit.
Definition: udisplay.cpp:221
unit_animation_component & anim_comp() const
Definition: unit.hpp:1533
int get_end_time() const
Definition: animation.cpp:1485
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:2203
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:2215
void pause_animation()
Definition: animation.cpp:1497
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
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:574
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:1364
unit_map::iterator find_unit(const map_location &loc)
Definition: game_board.hpp:177
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:218
const display_context & get_disp_context() const
Definition: display.hpp:172
void append(const unit_ability_list &other)
Appens the abilities from other to this, ignores other.loc()
Definition: unit.hpp:107
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2449
void reset_helpers(const unit *attacker, const unit *defender)
Definition: udisplay.cpp:772
int get_animation_time() const
Definition: animation.cpp:1475
#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:43
void set_all_standing()
Definition: animation.cpp:1515
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1346
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:1350
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition: udisplay.cpp:801
Standard logging facilities (interface).
fake_unit_ptr temp_unit_ptr_
Definition: udisplay.hpp:74
CVideo & video()
Gets the underlying screen object.
Definition: display.hpp:201
static const map_location & null_location()
Definition: location.hpp:81
Container associating units to locations.
Definition: map.hpp:98
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:188
bool valid() const
Definition: map.hpp:274
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
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()