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