The Battle for Wesnoth  1.19.25+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.resume_animation();
153 
154  auto target_time = animator.get_elapsed_time();
155  // target_time must be short to avoid jumpy move
156  // std::cout << "target time: " << target_time << "\n";
157  // we round it to the next multiple of 200 so that movement aligns to hex changes properly
158  target_time += 200ms;
159  target_time -= target_time % 200ms;
160 
161  return target_time;
162 }
163 
164 bool do_not_show_anims(display* disp)
165 {
167 }
168 
169 } // end anon namespace
170 
171 /**
172  * The path must remain unchanged for the life of this object.
173  */
174 unit_movement_animator::unit_movement_animator(const std::vector<map_location>& path, bool animate, bool force_scroll) :
175  disp_(game_display::get_singleton()),
176  can_draw_(disp_ && !video::headless() && path.size() > 1),
177  animate_(animate),
178  force_scroll_(force_scroll),
179  animator_(),
180  wait_until_(std::chrono::milliseconds::min()),
181  shown_unit_(),
182  path_(path),
183  current_(0),
184  temp_unit_ptr_(),
185  // Somewhat arbitrary default values.
186  was_hidden_(false),
187  is_enemy_(true)
188 {
189  // Some error conditions that indicate something has gone very wrong.
190  // (This class can handle these conditions, but someone wanted them
191  // to be assertions.)
192  assert(!path_.empty());
193  assert(disp_);
194 }
195 
196 
198 {
199  // Make sure a unit hidden for movement is unhidden.
201  // For safety, clear the animator before deleting the temp unit.
202  animator_.clear();
203 }
204 
205 
206 /**
207  * Makes the temporary unit used by this match the supplied unit.
208  * This is called when setting the initial unit, as well as replacing it with
209  * something new.
210  * When this finishes, the supplied unit is hidden, while the temporary unit
211  * is not hidden.
212  */
213 /* Note: Hide the unit in its current location; do not actually remove it.
214  * Otherwise the status displays will be wrong during the movement.
215  */
217 {
218  if ( disp_ == nullptr )
219  // No point in creating a temp unit with no way to display it.
220  return;
221 
222  // Save the hidden state of the unit.
223  was_hidden_ = u->get_hidden();
224 
225  // Make our temporary unit mostly match u...
227 
228  // ... but keep the temporary unhidden and hide the original.
229  temp_unit_ptr_->set_hidden(false);
230  u->set_hidden(true);
231 
232  // Update cached data.
234 }
235 
236 
237 /**
238  * Switches the display back to *shown_unit_ after animating.
239  * This uses temp_unit_ptr_, so (in the destructor) call this before deleting
240  * temp_unit_ptr_.
241  */
243 {
244  if ( shown_unit_ ) {
245  // Switch the display back to the real unit.
246  shown_unit_->set_hidden(was_hidden_);
247  temp_unit_ptr_->set_hidden(true);
248  shown_unit_.reset();
249  }
250 }
251 
252 
253 /**
254  * Initiates the display of movement for the supplied unit.
255  * This should be called before attempting to display moving to a new hex.
256  */
258 {
259  // Nothing to do here if there is nothing to animate.
260  if ( !can_draw_ )
261  return;
262  // If no animation then hide unit until end of movement
263  u->anim_comp().reset_affect_adjacent(*disp_);
264  if ( !animate_ ) {
265  was_hidden_ = u->get_hidden();
266  u->set_hidden(true);
267  return;
268  }
269 
270  // This normally does nothing, but just in case...
271  wait_for_anims();
272 
273  // Visually replace the original unit with the temporary.
274  // (Original unit is left on the map, so the unit count is correct.)
276 
277  // Initialize our temporary unit for the move.
278  temp_unit_ptr_->set_location(path_[0]);
279  temp_unit_ptr_->set_facing(path_[0].get_relative_dir(path_[1]));
280  temp_unit_ptr_->anim_comp().set_standing(false);
281  disp_->invalidate(path_[0]);
282 
283  // If the unit can be seen here by the viewing side:
284  if(!is_enemy_ || !temp_unit_ptr_->invisible(path_[0])) {
285  // Scroll to the path, but only if it fully fits on screen.
286  // If it does not fit we might be able to do a better scroll later.
287  disp_->scroll_to_tiles(path_, game_display::ONSCREEN, true, true, 0.0, false);
288  }
289 
290  // extra immobile movement animation for take-off
291  animator_.add_animation(temp_unit_ptr_.get_unit_ptr(), "pre_movement", path_[0], path_[1]);
294  animator_.clear();
295 
296  // Switch the display back to the real unit.
297  u->set_facing(temp_unit_ptr_->facing());
298  u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
299  u->set_hidden(was_hidden_);
300  temp_unit_ptr_->set_hidden(true);
301 }
302 
303 
304 /**
305  * Visually moves a unit from the last hex we drew to the one specified by
306  * @a path_index. If @a path_index points to an earlier hex, we do nothing.
307  * The moving unit will only be updated if update is set to true; otherwise,
308  * the provided unit is merely hidden during the movement and re-shown after.
309  * (Not updating the unit can produce smoother animations in some cases.)
310  * If @a wait is set to false, this returns without waiting for the final
311  * animation to finish. Call wait_for_anims() to explicitly get this final
312  * wait (another call to proceed_to() or finish() will implicitly wait). The
313  * unit must remain valid until the wait is finished.
314  */
315 void unit_movement_animator::proceed_to(const unit_ptr& u, std::size_t path_index, bool update, bool wait)
316 {
317  // Nothing to do here if animations cannot be shown.
318  if ( !can_draw_ || !animate_ )
319  return;
320 
321  // Handle pending visibility issues before introducing new ones.
322  wait_for_anims();
323 
324  if ( update || !temp_unit_ptr_ )
325  // Replace the temp unit (which also hides u and shows our temporary).
327  else
328  {
329  // Just switch the display from the real unit to our fake one.
330  temp_unit_ptr_->set_hidden(false);
331  u->set_hidden(true);
332  }
333 
334  // Safety check.
335  path_index = std::min(path_index, path_.size()-1);
336 
337  for ( ; current_ < path_index; ++current_ ) {
338  // It is possible for path_[current_] and path_[current_+1] not to be adjacent.
339  // When that is the case, and the unit is invisible at path_[current_], we shouldn't
340  // scroll to that hex.
341  std::vector<map_location> locs;
342  if (!temp_unit_ptr_->invisible(path_[current_]))
343  locs.push_back(path_[current_]);
344  if (!temp_unit_ptr_->invisible(path_[current_+1]))
345  locs.push_back(path_[current_+1]);
346  // If the unit can be seen by the viewing side while making this step:
347  if ( !is_enemy_ || !locs.empty() )
348  {
349  // Wait for the previous step to complete before drawing the next one.
350  wait_for_anims();
351 
354  {
355  // prevent the unit from disappearing if we scroll here with i == 0
356  temp_unit_ptr_->set_location(path_[current_]);
358  // scroll in as much of the remaining path as possible
359  if ( temp_unit_ptr_->anim_comp().get_animation() )
360  temp_unit_ptr_->anim_comp().get_animation()->pause_animation();
362  true, false, 0.0, force_scroll_);
363  if ( temp_unit_ptr_->anim_comp().get_animation() )
364  temp_unit_ptr_->anim_comp().get_animation()->resume_animation();
365  }
366 
368  wait_until_ =
369  move_unit_between(path_[current_], path_[current_+1],
371  path_.size() - (current_+2), animator_,
372  *disp_);
373  else if ( path_[current_] != path_[current_+1] )
374  teleport_unit_between(path_[current_], path_[current_+1],
375  *temp_unit_ptr_, *disp_);
376  }
377  }
378 
379  // Update the unit's facing.
380  u->set_facing(temp_unit_ptr_->facing());
381  u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
382  // Remember the unit to unhide when the animation finishes.
383  shown_unit_ = u;
384  if ( wait )
385  wait_for_anims();
386 }
387 
388 
389 /**
390  * Waits for the final animation of the most recent proceed_to() to finish.
391  * It is not necessary to call this unless you want to wait before the next
392  * call to proceed_to() or finish().
393  */
395 {
396  if ( wait_until_ == std::chrono::milliseconds::max() )
397  // Wait for end (not currently used, but still supported).
399  else if ( wait_until_ != std::chrono::milliseconds::min() ) {
400  // Wait until the specified time (used for normal movement).
402  // debug code, see unit_frame::redraw()
403  // std::cout << " end\n";
404  // TODO: For wesnoth 1.14+: check if efficient for redrawing?
405  // Check with large animated units too make sure artifacts are
406  // not left on screen after unit movement in particular.
407  if ( disp_ ) { // Should always be true if we get here.
408  // Invalidate the hexes around the move that prompted this wait.
409  for(const map_location& adj : get_adjacent_tiles(path_[current_ - 1])) {
410  disp_->invalidate(adj);
411  }
412 
413  for(const map_location& adj : get_adjacent_tiles(path_[current_])) {
414  disp_->invalidate(adj);
415  }
416  }
417  }
418 
419  // Reset data.
420  wait_until_ = std::chrono::milliseconds::min();
421  animator_.clear();
422 
424 }
425 
426 
427 /**
428  * Finishes the display of movement for the supplied unit.
429  * If called before showing the unit reach the end of the path, it will be
430  * assumed that the movement ended early.
431  * If @a dir is not supplied, the final direction will be determined by (the
432  * last two traversed hexes of) the path.
433  */
435 {
436  // Nothing to do here if the display is not valid.
437  if ( !can_draw_ ) {
438  // Make sure to reset the unit's animation to deal with a quirk in the
439  // action engine where it leaves it to us to reenable bars even if the
440  // display is initially locked.
441  u->anim_comp().set_standing(true);
442  return;
443  }
444 
445  const map_location & end_loc = path_[current_];
446  const map_location::direction final_dir = current_ == 0 ?
447  path_[0].get_relative_dir(path_[1]) :
448  path_[current_-1].get_relative_dir(end_loc);
449 
450  if ( animate_ )
451  {
452  wait_for_anims(); // In case proceed_to() did not wait for the last animation.
453 
454  // Make sure the displayed unit is correct.
456  temp_unit_ptr_->set_location(end_loc);
457  temp_unit_ptr_->set_facing(final_dir);
458 
459  // Animation
460  animator_.add_animation(temp_unit_ptr_.get_unit_ptr(), "post_movement", end_loc);
463  animator_.clear();
464 
465  // Switch the display back to the real unit.
466  u->set_hidden(was_hidden_);
467  temp_unit_ptr_->set_hidden(true);
468  u->anim_comp().reset_affect_adjacent(*disp_);
469 
471  mousehandler->invalidate_reachmap();
472  }
473  }
474  else
475  {
476  // Show the unit at end of skipped animation
477  u->set_hidden(was_hidden_);
478  }
479 
480  // Facing gets set even when not animating.
481  u->set_facing(dir == map_location::direction::indeterminate ? final_dir : dir);
482  u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
483 
484  // Redraw path ends (even if not animating).
485  disp_->invalidate(path_.front());
486  disp_->invalidate(end_loc);
487 }
488 
489 
490 /**
491  * Display a unit moving along a given path.
492  *
493  * @param path The path to traverse.
494  * @param u The unit to show being moved. Its facing will be updated,
495  * but not its position.
496  * @param animate If set to false, only side-effects of move are applied
497  * (correct unit facing, path hexes redrawing).
498  * @param dir Unit will be set facing this direction after move.
499  * If nothing passed, direction will be set based on path.
500  * @param force_scroll
501  */
502 /* Note: Hide the unit in its current location,
503  * but don't actually remove it until the move is done,
504  * so that while the unit is moving status etc.
505  * will still display the correct number of units.
506  */
507 void move_unit(const std::vector<map_location>& path, const unit_ptr& u,
508  bool animate, map_location::direction dir,
509  bool force_scroll)
510 {
511  unit_movement_animator mover(path, animate, force_scroll);
512 
513  mover.start(u);
514  mover.proceed_to(u, path.size());
515  mover.finish(u, dir);
516 }
517 
518 
519 void reset_helpers(const unit *attacker,const unit *defender);
520 
521 void unit_draw_weapon(const map_location& loc, unit& attacker,
522  const const_attack_ptr& attack,const const_attack_ptr& secondary_attack, const map_location& defender_loc, const unit_ptr& defender)
523 {
525  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
526  return;
527  }
528  unit_animator animator;
529  attacker.set_facing(loc.get_relative_dir(defender_loc));
530  defender->set_facing(defender_loc.get_relative_dir(loc));
531  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);
532  if(defender) {
533  animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},strike_result::type::miss,secondary_attack,attack,0);
534  }
535  animator.start_animations();
536  animator.wait_for_end();
537 
538 }
539 
540 
541 void unit_sheath_weapon(const map_location& primary_loc, const unit_ptr& primary_unit,
542  const const_attack_ptr& primary_attack,const const_attack_ptr& secondary_attack, const map_location& secondary_loc,const unit_ptr& secondary_unit)
543 {
545  if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !prefs::get().show_combat()) {
546  return;
547  }
548  unit_animator animator;
549  if(primary_unit) {
550  animator.add_animation(primary_unit,"sheath_weapon",primary_loc,secondary_loc,0,true,"",{0,0,0},strike_result::type::invalid,primary_attack,secondary_attack,0);
551  }
552  if(secondary_unit) {
553  animator.add_animation(secondary_unit,"sheath_weapon",secondary_loc,primary_loc,0,true,"",{0,0,0},strike_result::type::invalid,secondary_attack,primary_attack,0);
554  }
555 
556  if(primary_unit || secondary_unit) {
557  animator.start_animations();
558  animator.wait_for_end();
559  }
560  if(primary_unit) {
561  primary_unit->anim_comp().set_standing();
562  }
563  if(secondary_unit) {
564  secondary_unit->anim_comp().set_standing();
565  }
566  reset_helpers(primary_unit.get(),secondary_unit.get());
567 
568 }
569 
570 
571 void unit_die(const map_location& loc, unit& loser,
572  const const_attack_ptr& attack,const const_attack_ptr& secondary_attack, const map_location& winner_loc, const unit_ptr& winner)
573 {
575  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
576  return;
577  }
578  unit_animator animator;
579  // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
580  animator.add_animation(loser.shared_from_this(),"death",loc,winner_loc,0,false,"",{0,0,0},strike_result::type::kill,attack,secondary_attack,0);
581  // but show the bars of the winner (avoid blinking and show its xp gain)
582  if(winner) {
583  animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
584  strike_result::type::kill,secondary_attack,attack,0);
585  }
586  animator.start_animations();
587  animator.wait_for_end();
588 
589  reset_helpers(winner.get(), &loser);
590 
592  mousehandler->invalidate_reachmap();
593  }
594  loser.anim_comp().reset_affect_adjacent(*disp);
595 }
596 
597 
598 void unit_attack(display * disp, game_board & board,
599  const map_location& a, const map_location& b, int damage,
600  const attack_type& attack, const const_attack_ptr& secondary_attack,
601  int swing, const std::string& hit_text, int drain_amount,
602  const std::string& att_text,
603  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)) || !prefs::get().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 
627  auto ctx = specials_context_t::make(
628  { attacker.shared_from_this(), a, weapon },
629  { defender.shared_from_this(), b, secondary_attack },
630  attacking);
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  strike_result::type hit_type;
640  if(damage >= defender.hitpoints()) {
641  hit_type = strike_result::type::kill;
642  } else if(damage > 0) {
643  hit_type = strike_result::type::hit;
644  }else {
645  hit_type = strike_result::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(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  active_ability_list leadership_list = ctx.get_abilities_weapons("leadership", attacker);
661  active_ability_list resistance_list = ctx.get_abilities_weapons("resistance", defender);
662  for(const active_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  leader->anim_comp().invalidate(*disp);
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 active_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  active_ability_list abilities = ctx.get_active_combat_teachers(*weapon);
698 
699  for(const active_ability& ability : abilities) {
700  if(ability.teacher_loc == a) {
701  continue;
702  }
703 
704  if(ability.teacher_loc == b) {
705  continue;
706  }
707 
708  bool leading_playable = false;
709  bool helping_playable = false;
710  for(const active_ability& leader_list : leadership_list) {
711  if(ability.teacher_loc == leader_list.teacher_loc) {
712  leading_playable = true;
713  break;
714  }
715  }
716 
717  for(const active_ability& helper_list : resistance_list) {
718  if(ability.teacher_loc == helper_list.teacher_loc) {
719  helping_playable = true;
720  break;
721  }
722  }
723 
724  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
725  assert(leader.valid());
726  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
727  if(animator.has_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
728  att->get_location(), damage, hit_type, weapon, secondary_attack, swing) && leading_playable){
729  continue;
730  }
731  if(animator.has_animation(leader.get_shared_ptr(), "resistance", ability.teacher_loc,
732  def->get_location(), damage, hit_type, weapon, secondary_attack, swing) && helping_playable){
733  continue;
734  }
735  animator.add_animation(leader.get_shared_ptr(), "teaching", ability.teacher_loc,
736  att->get_location(), damage, true, "", {0,0,0},
737  hit_type, weapon, secondary_attack, swing);
738  }
739 
740 
741  animator.start_animations();
742  animator.wait_until(0ms);
743  int damage_left = damage;
744  bool extra_hit_sounds_played = false;
745  while(damage_left > 0 && !animator.would_end()) {
746  if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
747  for (std::string hit_sound : *extra_hit_sounds) {
748  sound::play_sound(hit_sound);
749  }
750  extra_hit_sounds_played = true;
751  }
752 
753  auto step_left = (animator.get_end_time() - animator.get_elapsed_time() ) / 50ms;
754  if(step_left < 1) step_left = 1;
755  int removed_hp = damage_left/step_left ;
756  if(removed_hp < 1) removed_hp = 1;
757  defender.take_hit(removed_hp);
758  damage_left -= removed_hp;
759  animator.wait_until(animator.get_elapsed_time() + 50ms);
760  }
761  animator.wait_for_end();
762  // pass the animation back to the real unit
763  def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
764  reset_helpers(&*att, &*def);
765  def->set_hitpoints(def_hitpoints);
766 }
767 
768 // private helper function, set all helpers to default position
769 void reset_helpers(const unit *attacker,const unit *defender)
770 {
772  const unit_map& units = disp->context().units();
773  if(attacker) {
774  active_ability_list attacker_abilities = attacker->get_abilities("leadership");
775  for(auto& special : abilities_list::all_weapon_tags()) {
776  attacker_abilities.append(attacker->get_abilities(special));
777  }
778  for(const active_ability& ability : attacker_abilities) {
779  unit_map::const_iterator leader = units.find(ability.teacher_loc);
780  assert(leader != units.end());
781  leader->anim_comp().set_standing();
782  }
783  }
784 
785  if(defender) {
786  active_ability_list defender_abilities = defender->get_abilities("resistance");
787  for(auto& special : abilities_list::all_weapon_tags()) {
788  defender_abilities.append(defender->get_abilities(special));
789  }
790  for(const active_ability& ability : defender_abilities) {
791  unit_map::const_iterator helper = units.find(ability.teacher_loc);
792  assert(helper != units.end());
793  helper->anim_comp().set_standing();
794  }
795  }
796 }
797 
798 void unit_recruited(const map_location& loc,const map_location& leader_loc)
799 {
801  if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
802  return;
803  }
804 
805  const team& viewing_team = disp->viewing_team();
806  const unit_map& units = disp->context().units();
807 
808  unit_map::const_iterator u = units.find(loc);
809  if(u == units.end()) return;
810  const bool unit_visible = u->is_visible_to_team(viewing_team, false);
811 
812  unit_map::const_iterator leader = units.find(leader_loc); // may be null_location
813  const bool leader_visible = (leader != units.end()) && leader->is_visible_to_team(viewing_team, false);
814 
815  unit_animator animator;
816 
817  {
818  ON_SCOPE_EXIT(u) {
819  u->set_hidden(false);
820  };
821  u->set_hidden(true);
822 
823  if (leader_visible && unit_visible) {
824  disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
825  } else if (leader_visible) {
826  disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
827  } else if (unit_visible) {
828  disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
829  } else {
830  return;
831  }
832  if(leader != units.end()) {
833  leader->set_facing(leader_loc.get_relative_dir(loc));
834  if (leader_visible) {
835  animator.add_animation(leader.get_shared_ptr(), "recruiting", leader_loc, loc, 0, true);
836  }
837  }
838  }
839  u->anim_comp().reset_affect_adjacent(*disp);
840  animator.add_animation(u.get_shared_ptr(), "recruited", loc, leader_loc);
841  animator.start_animations();
842  animator.wait_for_end();
843  animator.set_all_standing();
844  if (loc==disp->mouseover_hex()) disp->invalidate_unit();
845 }
846 
847 void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
848  const std::string & extra_text)
849 {
851  const map_location& healed_loc = healed.get_location();
852  const bool some_healer_is_unfogged =
853  (healers.end() != std::find_if_not(healers.begin(), healers.end(),
854  [&](unit* h) { return disp->fogged(h->get_location()); }));
855 
856  if(do_not_show_anims(disp) || (disp->fogged(healed_loc) && !some_healer_is_unfogged)) {
857  return;
858  }
859 
860  // This is all the pretty stuff.
861  disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
862  disp->display_unit_hex(healed_loc);
863  unit_animator animator;
864 
865  for (unit *h : healers) {
866  h->set_facing(h->get_location().get_relative_dir(healed_loc));
867  animator.add_animation(h->shared_from_this(), "healing", h->get_location(),
868  healed_loc, healing);
869  }
870 
871  if (healing < 0) {
872  animator.add_animation(healed.shared_from_this(), "poisoned", healed_loc,
873  map_location::null_location(), -healing, false,
874  number_and_text(-healing, extra_text),
875  {255,0,0});
876  } else if ( healing > 0 ) {
877  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
878  map_location::null_location(), healing, false,
879  number_and_text(healing, extra_text),
880  {0,255,0});
881  } else {
882  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
883  map_location::null_location(), 0, false,
884  extra_text, {0,255,0});
885  }
886  animator.start_animations();
887  animator.wait_for_end();
888  animator.set_all_standing();
889 }
890 
891 } // end unit_display namespace
map_location loc
Definition: move.cpp:172
void append(const active_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
Definition: abilities.hpp:237
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:332
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2970
@ ONSCREEN
Definition: display.hpp:494
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:1858
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:664
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:1766
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:1868
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:299
virtual void select_hex(map_location hex)
Definition: display.cpp:1375
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()
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
Definition: abilities.hpp:275
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 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_elapsed_time() const
Definition: animation.cpp:1463
void pause_animation()
Definition: animation.cpp:1483
void resume_animation()
Definition: animation.cpp:1492
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:1343
void wait_for_end() const
Definition: animation.cpp:1448
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:1356
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:1330
bool would_end() const
Definition: animation.cpp:1406
void start_animations()
Definition: animation.cpp:1382
void set_all_standing()
Definition: animation.cpp:1501
std::chrono::milliseconds get_end_time() const
Definition: animation.cpp:1471
void wait_until(const std::chrono::milliseconds &animation_time) const
Definition: animation.cpp:1416
A class to encapsulate the steps of drawing a unit's move.
Definition: udisplay.hpp:45
unit_movement_animator(const unit_movement_animator &)=delete
std::chrono::milliseconds wait_until_
The animation potential to wait until.
Definition: udisplay.hpp:69
void replace_temporary(const unit_ptr &u)
Makes the temporary unit used by this match the supplied unit.
Definition: udisplay.cpp:216
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:315
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition: udisplay.cpp:242
unit_ptr shown_unit_
The unit to be (re-)shown after an animation finishes.
Definition: udisplay.hpp:71
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:394
const std::vector< map_location > & path_
Definition: udisplay.hpp:72
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:434
void start(const unit_ptr &u)
Initiates the display of movement for the supplied unit.
Definition: udisplay.cpp:257
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:39
active_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:708
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2673
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:421
bool take_hit(int damage)
Damage the unit.
Definition: unit.hpp:731
unit_animation_component & anim_comp() const
Definition: unit.hpp:1537
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
void set_facing(map_location::direction dir) const
The this unit's facing.
Definition: unit.cpp:1732
void set_location(const map_location &loc)
Sets this unit's map location.
Definition: unit.hpp:1337
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:513
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:541
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, sound_tracks::type group, unsigned int repeats)
Definition: sound.cpp:930
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:571
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:521
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:598
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:847
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition: udisplay.cpp:798
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:507
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:541
void reset_helpers(const unit *attacker, const unit *defender)
Definition: udisplay.cpp:769
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
bool headless()
The game is running headless.
Definition: video.cpp:146
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
Data typedef for active_ability_list.
Definition: abilities.hpp:165
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
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
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