The Battle for Wesnoth  1.19.14+dev
udisplay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
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_ptr.hpp"
19 #include "game_board.hpp"
20 #include "game_display.hpp"
22 #include "log.hpp"
23 #include "mouse_events.hpp"
24 #include "resources.hpp"
25 #include "play_controller.hpp"
26 #include "color.hpp"
27 #include "sound.hpp"
28 #include "units/ability_tags.hpp"
29 #include "units/unit.hpp"
31 #include "units/map.hpp"
32 #include "utils/scope_exit.hpp"
33 #include "video.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 team& viewing_team = disp.viewing_team();
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  temp_unit.anim_comp().reset_affect_adjacent(disp);
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  temp_unit.anim_comp().reset_affect_adjacent(disp);
110  }
111 
112  temp_unit.anim_comp().set_standing();
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  * milliseconds::min indicates that no animation is pending.
131  */
132 std::chrono::milliseconds move_unit_between(const map_location& a,
133  const map_location& b,
134  const 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 std::chrono::milliseconds::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},strike_result::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  auto 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 += 200ms;
162  target_time -= target_time % 200ms;
163 
164  return target_time;
165 }
166 
167 bool do_not_show_anims(display* disp)
168 {
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_ && !video::headless() && path.size() > 1),
180  animate_(animate),
181  force_scroll_(force_scroll),
182  animator_(),
183  wait_until_(std::chrono::milliseconds::min()),
184  shown_unit_(),
185  path_(path),
186  current_(0),
187  temp_unit_ptr_(),
188  // Somewhat arbitrary default values.
189  was_hidden_(false),
190  is_enemy_(true)
191 {
192  // Some error conditions that indicate something has gone very wrong.
193  // (This class can handle these conditions, but someone wanted them
194  // to be assertions.)
195  assert(!path_.empty());
196  assert(disp_);
197 }
198 
199 
201 {
202  // Make sure a unit hidden for movement is unhidden.
204  // For safety, clear the animator before deleting the temp unit.
205  animator_.clear();
206 }
207 
208 
209 /**
210  * Makes the temporary unit used by this match the supplied unit.
211  * This is called when setting the initial unit, as well as replacing it with
212  * something new.
213  * When this finishes, the supplied unit is hidden, while the temporary unit
214  * is not hidden.
215  */
216 /* Note: Hide the unit in its current location; do not actually remove it.
217  * Otherwise the status displays will be wrong during the movement.
218  */
220 {
221  if ( disp_ == nullptr )
222  // No point in creating a temp unit with no way to display it.
223  return;
224 
225  // Save the hidden state of the unit.
226  was_hidden_ = u->get_hidden();
227 
228  // Make our temporary unit mostly match u...
230 
231  // ... but keep the temporary unhidden and hide the original.
232  temp_unit_ptr_->set_hidden(false);
233  u->set_hidden(true);
234 
235  // Update cached data.
237 }
238 
239 
240 /**
241  * Switches the display back to *shown_unit_ after animating.
242  * This uses temp_unit_ptr_, so (in the destructor) call this before deleting
243  * temp_unit_ptr_.
244  */
246 {
247  if ( shown_unit_ ) {
248  // Switch the display back to the real unit.
249  shown_unit_->set_hidden(was_hidden_);
250  temp_unit_ptr_->set_hidden(true);
251  shown_unit_.reset();
252  }
253 }
254 
255 
256 /**
257  * Initiates the display of movement for the supplied unit.
258  * This should be called before attempting to display moving to a new hex.
259  */
261 {
262  // Nothing to do here if there is nothing to animate.
263  if ( !can_draw_ )
264  return;
265  // If no animation then hide unit until end of movement
266  u->anim_comp().reset_affect_adjacent(*disp_);
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(const 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 
357  {
358  // prevent the unit from disappearing if we scroll here with i == 0
359  temp_unit_ptr_->set_location(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 
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_ == std::chrono::milliseconds::max() )
400  // Wait for end (not currently used, but still supported).
402  else if ( wait_until_ != std::chrono::milliseconds::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_ = std::chrono::milliseconds::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  u->anim_comp().reset_affect_adjacent(*disp_);
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::direction::indeterminate ? 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, const 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 const_attack_ptr& attack,const const_attack_ptr& secondary_attack, const map_location& defender_loc, const unit_ptr& defender)
526 {
528  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().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},strike_result::type::hit,attack,secondary_attack,0);
535  if(defender) {
536  animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},strike_result::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, const unit_ptr& primary_unit,
545  const const_attack_ptr& primary_attack,const const_attack_ptr& secondary_attack, const map_location& secondary_loc,const unit_ptr& secondary_unit)
546 {
548  if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !prefs::get().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},strike_result::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},strike_result::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 const_attack_ptr& attack,const const_attack_ptr& secondary_attack, const map_location& winner_loc, const unit_ptr& winner)
576 {
578  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().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},strike_result::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  strike_result::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  loser.anim_comp().reset_affect_adjacent(*disp);
598 }
599 
600 
601 void unit_attack(display * disp, game_board & board,
602  const map_location& a, const map_location& b, int damage,
603  const attack_type& attack, const const_attack_ptr& secondary_attack,
604  int swing, const std::string& hit_text, int drain_amount,
605  const std::string& att_text,
606  const std::vector<std::string>* extra_hit_sounds,
607  bool attacking)
608 {
609  if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !prefs::get().show_combat()) {
610  return;
611  }
612  //const unit_map& units = disp->get_units();
614 
615  // scroll such that there is at least half a hex spacing around fighters
616  disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
617 
618  log_scope("unit_attack");
619 
620  const unit_map::const_iterator att = board.units().find(a);
621  assert(att.valid());
622  const unit& attacker = *att;
623 
624  const unit_map::iterator def = board.find_unit(b);
625  assert(def.valid());
626  unit &defender = *def;
627  int def_hitpoints = defender.hitpoints();
628  const_attack_ptr weapon = attack.shared_from_this();
629  auto ctx = weapon->specials_context(attacker.shared_from_this(), defender.shared_from_this(), a, b, attacking, secondary_attack);
630  utils::optional<decltype(ctx)> opp_ctx;
631 
632  if(secondary_attack) {
633  opp_ctx.emplace(secondary_attack->specials_context(defender.shared_from_this(), attacker.shared_from_this(), b, a, !attacking, weapon));
634  }
635 
636  att->set_facing(a.get_relative_dir(b));
637  def->set_facing(b.get_relative_dir(a));
638  defender.set_facing(b.get_relative_dir(a));
639 
640  std::string text = number_and_text(damage, hit_text);
641  std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
642 
643  strike_result::type hit_type;
644  if(damage >= defender.hitpoints()) {
645  hit_type = strike_result::type::kill;
646  } else if(damage > 0) {
647  hit_type = strike_result::type::hit;
648  }else {
649  hit_type = strike_result::type::miss;
650  }
651 
652  unit_animator animator;
653 
654  animator.add_animation(attacker.shared_from_this(), "attack", att->get_location(), def->get_location(), damage, true, text_2,
655  (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, weapon,
656  secondary_attack, swing);
657 
658  // note that we take an anim from the real unit, we'll use it later
659  const unit_animation* defender_anim = def->anim_comp().choose_animation(def->get_location(), "defend",
660  att->get_location(), damage, hit_type, weapon, secondary_attack, swing);
661 
662  animator.add_animation(defender.shared_from_this(), defender_anim, def->get_location(), true, text, {255, 0, 0});
663 
664  unit_ability_list leadership_list = attacker.get_abilities_weapons("leadership", weapon, secondary_attack);
665  unit_ability_list resistance_list = defender.get_abilities_weapons("resistance", secondary_attack, weapon);
666  for(const unit_ability& ability : leadership_list) {
667  if(ability.teacher_loc == a) {
668  continue;
669  }
670 
671  if(ability.teacher_loc == b) {
672  continue;
673  }
674 
675  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
676  assert(leader.valid());
677  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
678  leader->anim_comp().invalidate(*disp);
679  animator.add_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
680  att->get_location(), damage, true, "", {0,0,0},
681  hit_type, weapon, secondary_attack, swing);
682  }
683 
684  for(const unit_ability& ability : resistance_list) {
685  if(ability.teacher_loc == a) {
686  continue;
687  }
688 
689  if(ability.teacher_loc == b) {
690  continue;
691  }
692 
693  unit_map::const_iterator helper = board.units().find(ability.teacher_loc);
694  assert(helper.valid());
695  helper->set_facing(ability.teacher_loc.get_relative_dir(b));
696  animator.add_animation(helper.get_shared_ptr(), "resistance", ability.teacher_loc,
697  def->get_location(), damage, true, "", {0,0,0},
698  hit_type, weapon, secondary_attack, swing);
699  }
700 
701  unit_ability_list abilities = att->get_location();
702  for(auto& special : abilities_list::all_weapon_tags()) {
703  abilities.append(weapon->get_weapon_ability(special));
704  }
705 
706  for(const unit_ability& ability : abilities) {
707  if(ability.teacher_loc == a) {
708  continue;
709  }
710 
711  if(ability.teacher_loc == b) {
712  continue;
713  }
714 
715  bool leading_playable = false;
716  bool helping_playable = false;
717  for(const unit_ability& leader_list : leadership_list) {
718  if(ability.teacher_loc == leader_list.teacher_loc) {
719  leading_playable = true;
720  break;
721  }
722  }
723 
724  for(const unit_ability& helper_list : resistance_list) {
725  if(ability.teacher_loc == helper_list.teacher_loc) {
726  helping_playable = true;
727  break;
728  }
729  }
730 
731  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
732  assert(leader.valid());
733  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
734  if(animator.has_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
735  att->get_location(), damage, hit_type, weapon, secondary_attack, swing) && leading_playable){
736  continue;
737  }
738  if(animator.has_animation(leader.get_shared_ptr(), "resistance", ability.teacher_loc,
739  def->get_location(), damage, hit_type, weapon, secondary_attack, swing) && helping_playable){
740  continue;
741  }
742  animator.add_animation(leader.get_shared_ptr(), "teaching", ability.teacher_loc,
743  att->get_location(), damage, true, "", {0,0,0},
744  hit_type, weapon, secondary_attack, swing);
745  }
746 
747 
748  animator.start_animations();
749  animator.wait_until(0ms);
750  int damage_left = damage;
751  bool extra_hit_sounds_played = false;
752  while(damage_left > 0 && !animator.would_end()) {
753  if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
754  for (std::string hit_sound : *extra_hit_sounds) {
755  sound::play_sound(hit_sound);
756  }
757  extra_hit_sounds_played = true;
758  }
759 
760  auto step_left = (animator.get_end_time() - animator.get_animation_time() ) / 50ms;
761  if(step_left < 1) step_left = 1;
762  int removed_hp = damage_left/step_left ;
763  if(removed_hp < 1) removed_hp = 1;
764  defender.take_hit(removed_hp);
765  damage_left -= removed_hp;
766  animator.wait_until(animator.get_animation_time_potential() + 50ms);
767  }
768  animator.wait_for_end();
769  // pass the animation back to the real unit
770  def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
771  reset_helpers(&*att, &*def);
772  def->set_hitpoints(def_hitpoints);
773 }
774 
775 // private helper function, set all helpers to default position
776 void reset_helpers(const unit *attacker,const unit *defender)
777 {
779  const unit_map& units = disp->context().units();
780  if(attacker) {
781  unit_ability_list attacker_abilities = attacker->get_abilities("leadership");
782  for(auto& special : abilities_list::all_weapon_tags()) {
783  attacker_abilities.append(attacker->get_abilities(special));
784  }
785  for(const unit_ability& ability : attacker_abilities) {
786  unit_map::const_iterator leader = units.find(ability.teacher_loc);
787  assert(leader != units.end());
788  leader->anim_comp().set_standing();
789  }
790  }
791 
792  if(defender) {
793  unit_ability_list defender_abilities = defender->get_abilities("resistance");
794  for(auto& special : abilities_list::all_weapon_tags()) {
795  defender_abilities.append(defender->get_abilities(special));
796  }
797  for(const unit_ability& ability : defender_abilities) {
798  unit_map::const_iterator helper = units.find(ability.teacher_loc);
799  assert(helper != units.end());
800  helper->anim_comp().set_standing();
801  }
802  }
803 }
804 
805 void unit_recruited(const map_location& loc,const map_location& leader_loc)
806 {
808  if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
809  return;
810  }
811 
812  const team& viewing_team = disp->viewing_team();
813  const unit_map& units = disp->context().units();
814 
815  unit_map::const_iterator u = units.find(loc);
816  if(u == units.end()) return;
817  const bool unit_visible = u->is_visible_to_team(viewing_team, false);
818 
819  unit_map::const_iterator leader = units.find(leader_loc); // may be null_location
820  const bool leader_visible = (leader != units.end()) && leader->is_visible_to_team(viewing_team, false);
821 
822  unit_animator animator;
823 
824  {
825  ON_SCOPE_EXIT(u) {
826  u->set_hidden(false);
827  };
828  u->set_hidden(true);
829 
830  if (leader_visible && unit_visible) {
831  disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
832  } else if (leader_visible) {
833  disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
834  } else if (unit_visible) {
835  disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
836  } else {
837  return;
838  }
839  if(leader != units.end()) {
840  leader->set_facing(leader_loc.get_relative_dir(loc));
841  if (leader_visible) {
842  animator.add_animation(leader.get_shared_ptr(), "recruiting", leader_loc, loc, 0, true);
843  }
844  }
845  }
846  u->anim_comp().reset_affect_adjacent(*disp);
847  animator.add_animation(u.get_shared_ptr(), "recruited", loc, leader_loc);
848  animator.start_animations();
849  animator.wait_for_end();
850  animator.set_all_standing();
851  if (loc==disp->mouseover_hex()) disp->invalidate_unit();
852 }
853 
854 void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
855  const std::string & extra_text)
856 {
858  const map_location& healed_loc = healed.get_location();
859  const bool some_healer_is_unfogged =
860  (healers.end() != std::find_if_not(healers.begin(), healers.end(),
861  [&](unit* h) { return disp->fogged(h->get_location()); }));
862 
863  if(do_not_show_anims(disp) || (disp->fogged(healed_loc) && !some_healer_is_unfogged)) {
864  return;
865  }
866 
867  // This is all the pretty stuff.
868  disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
869  disp->display_unit_hex(healed_loc);
870  unit_animator animator;
871 
872  for (unit *h : healers) {
873  h->set_facing(h->get_location().get_relative_dir(healed_loc));
874  animator.add_animation(h->shared_from_this(), "healing", h->get_location(),
875  healed_loc, healing);
876  }
877 
878  if (healing < 0) {
879  animator.add_animation(healed.shared_from_this(), "poisoned", healed_loc,
880  map_location::null_location(), -healing, false,
881  number_and_text(-healing, extra_text),
882  {255,0,0});
883  } else if ( healing > 0 ) {
884  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
885  map_location::null_location(), healing, false,
886  number_and_text(healing, extra_text),
887  {0,255,0});
888  } else {
889  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
890  map_location::null_location(), 0, false,
891  extra_text, {0,255,0});
892  }
893  animator.start_animations();
894  animator.wait_for_end();
895  animator.set_all_standing();
896 }
897 
898 } // end unit_display namespace
map_location loc
Definition: move.cpp:172
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
virtual const unit_map & units() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
const team & viewing_team() const
Definition: display.cpp:331
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2960
@ ONSCREEN
Definition: display.hpp:496
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:1854
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:663
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:1762
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:1864
const display_context & context() const
Definition: display.hpp:184
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
const map_location & mouseover_hex() const
Definition: display.hpp:301
virtual void select_hex(map_location hex)
Definition: display.cpp:1371
static mouse_handler * get_singleton()
Holds a temporary unit that can be drawn on the map without being placed in the unit_map.
internal_ptr get_unit_ptr()
Get a copy of the internal unit pointer.
Game board class.
Definition: game_board.hpp:47
team & get_team(int i)
Definition: game_board.hpp:92
unit_map::iterator find_unit(const map_location &loc)
Definition: game_board.hpp:170
virtual const unit_map & units() const override
Definition: game_board.hpp:107
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
bool is_skipping_replay() const
static prefs & get()
bool show_combat()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
int side() const
Definition: team.hpp:179
bool is_enemy(int n) const
Definition: team.hpp:267
void append(const unit_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
Definition: unit.hpp:105
void reset_affect_adjacent(const unit_map &units)
Refresh map around unit if has ability with [affect_adjacent/distant] tag.
void set_standing(bool with_bars=true)
Sets the animation state to standing.
std::chrono::milliseconds get_animation_time_potential() const
Definition: animation.cpp:1461
void pause_animation()
Definition: animation.cpp:1481
std::chrono::milliseconds get_animation_time() const
Definition: animation.cpp:1453
bool has_animation(const 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 strike_result::type hit_type=strike_result::type::invalid, const const_attack_ptr &attack=nullptr, const 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:1333
void wait_for_end() const
Definition: animation.cpp:1438
void replace_anim_if_invalid(const 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 strike_result::type hit_type=strike_result::type::invalid, const const_attack_ptr &attack=nullptr, const const_attack_ptr &second_attack=nullptr, int value2=0)
Definition: animation.cpp:1346
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:1320
bool would_end() const
Definition: animation.cpp:1396
void start_animations()
Definition: animation.cpp:1372
void set_all_standing()
Definition: animation.cpp:1499
std::chrono::milliseconds get_end_time() const
Definition: animation.cpp:1469
void restart_animation()
Definition: animation.cpp:1490
void wait_until(const std::chrono::milliseconds &animation_time) const
Definition: animation.cpp:1406
A class to encapsulate the steps of drawing a unit's move.
Definition: udisplay.hpp:45
const bool force_scroll_
Definition: udisplay.hpp:66
void finish(const unit_ptr &u, map_location::direction dir=map_location::direction::indeterminate)
Finishes the display of movement for the supplied unit.
Definition: udisplay.cpp:437
void replace_temporary(const unit_ptr &u)
Makes the temporary unit used by this match the supplied unit.
Definition: udisplay.cpp:219
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:397
unit_ptr shown_unit_
The unit to be (re-)shown after an animation finishes.
Definition: udisplay.hpp:71
unit_animator animator_
Definition: udisplay.hpp:67
game_display *const disp_
Definition: udisplay.hpp:63
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition: udisplay.cpp:245
std::chrono::milliseconds wait_until_
The animation potential to wait until.
Definition: udisplay.hpp:69
void proceed_to(const 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 start(const unit_ptr &u)
Initiates the display of movement for the supplied unit.
Definition: udisplay.cpp:260
const std::vector< map_location > & path_
Definition: udisplay.hpp:72
unit_mover(const unit_mover &)=delete
fake_unit_ptr temp_unit_ptr_
Definition: udisplay.hpp:74
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
This class represents a single unit of a specific type.
Definition: unit.hpp:132
unit_ability_list get_abilities_weapons(const std::string &tag_name, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
Definition: abilities.cpp:293
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2652
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:251
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:514
bool take_hit(int damage)
Damage the unit.
Definition: unit.hpp:824
unit_animation_component & anim_comp() const
Definition: unit.hpp:1641
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1431
void set_facing(map_location::direction dir) const
The this unit's facing.
Definition: unit.cpp:1719
void set_location(const map_location &loc)
Sets this unit's map location.
Definition: unit.hpp:1441
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:512
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:540
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:275
static void update()
void pump()
Process all events currently in the queue.
Definition: events.cpp:480
std::string path
Definition: filesystem.cpp:106
game_board * gameboard
Definition: resources.cpp:20
fake_unit_manager * fake_units
Definition: resources.cpp:30
play_controller * controller
Definition: resources.cpp:21
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:39
void unit_die(const map_location &loc, unit &loser, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &winner_loc, const unit_ptr &winner)
Show a unit fading out.
Definition: udisplay.cpp:574
void unit_draw_weapon(const map_location &loc, unit &attacker, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &defender_loc, const unit_ptr &defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
Definition: udisplay.cpp:524
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const 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 'a' attack the unit on tile 'b'.
Definition: udisplay.cpp:601
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:854
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition: udisplay.cpp:805
void move_unit(const std::vector< map_location > &path, const unit_ptr &u, bool animate, map_location::direction dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:510
void unit_sheath_weapon(const map_location &primary_loc, const unit_ptr &primary_unit, const const_attack_ptr &primary_attack, const const_attack_ptr &secondary_attack, const map_location &secondary_loc, const 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
void reset_helpers(const unit *attacker, const unit *defender)
Definition: udisplay.cpp:776
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
bool headless()
The game is running headless.
Definition: video.cpp:147
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
#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
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:61
Encapsulates the map of the game.
Definition: location.hpp:46
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:238
static const map_location & null_location()
Definition: location.hpp:103
Data typedef for unit_ability_list.
Definition: unit.hpp:37
bool valid() const
Definition: map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:217
Display units performing various actions: moving, attacking, and dying.
#define h
#define b