The Battle for Wesnoth  1.19.5+dev
udisplay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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  }
94 
95  temp_unit.set_location(b);
96  if ( b_visible ) { // teleport
97  disp.invalidate(b);
98  temp_unit.set_facing(a.get_relative_dir(b));
99  if ( a_visible )
100  disp.scroll_to_tiles(b, a, game_display::ONSCREEN, true, 0.0, false);
101  else
102  disp.scroll_to_tile(b, game_display::ONSCREEN, true, false);
103  unit_animator animator;
104  animator.add_animation(temp_unit.shared_from_this(),"post_teleport",b);
105  animator.start_animations();
106  animator.wait_for_end();
107  }
108 
109  temp_unit.anim_comp().set_standing();
110  events::pump();
111 }
112 
113 /**
114  * Animates a single step between hexes.
115  * This will return before the animation actually finishes, allowing other
116  * processing to occur during the animation.
117  *
118  * @param a The starting hex.
119  * @param b The ending hex.
120  * @param temp_unit The unit to animate (historically, a temporary unit).
121  * @param step_num The number of steps taken so far (used to pick an animation).
122  * @param step_left The number of steps remaining (used to pick an animation).
123  * @param animator The unit_animator to use. This is assumed clear when we start,
124  * but will likely not be clear when we return.
125  * @param disp The game display. Assumed neither locked nor faked.
126  * @returns The animation potential until this animation will finish.
127  * milliseconds::min indicates that no animation is pending.
128  */
129 std::chrono::milliseconds move_unit_between(const map_location& a,
130  const map_location& b,
131  unit_ptr temp_unit,
132  unsigned int step_num,
133  unsigned int step_left,
134  unit_animator& animator,
135  display& disp)
136 {
137  if ( disp.fogged(a) && disp.fogged(b) ) {
138  return std::chrono::milliseconds::min();
139  }
140 
141  temp_unit->set_location(a);
142  disp.invalidate(a);
143  temp_unit->set_facing(a.get_relative_dir(b));
144  animator.replace_anim_if_invalid(temp_unit,"movement",a,b,step_num,
145  false,"",{0,0,0},strike_result::type::invalid,nullptr,nullptr,step_left);
146  animator.start_animations();
147  animator.pause_animation();
148  disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
149  animator.restart_animation();
150 
151  // useless now, previous short draw() just did one
152  // new_animation_frame();
153 
154  auto target_time = animator.get_animation_time_potential();
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_mover::unit_mover(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  if ( !animate_ ) {
264  was_hidden_ = u->get_hidden();
265  u->set_hidden(true);
266  return;
267  }
268 
269  // This normally does nothing, but just in case...
270  wait_for_anims();
271 
272  // Visually replace the original unit with the temporary.
273  // (Original unit is left on the map, so the unit count is correct.)
275 
276  // Initialize our temporary unit for the move.
277  temp_unit_ptr_->set_location(path_[0]);
278  temp_unit_ptr_->set_facing(path_[0].get_relative_dir(path_[1]));
279  temp_unit_ptr_->anim_comp().set_standing(false);
280  disp_->invalidate(path_[0]);
281 
282  // If the unit can be seen here by the viewing side:
283  if(!is_enemy_ || !temp_unit_ptr_->invisible(path_[0])) {
284  // Scroll to the path, but only if it fully fits on screen.
285  // If it does not fit we might be able to do a better scroll later.
286  disp_->scroll_to_tiles(path_, game_display::ONSCREEN, true, true, 0.0, false);
287  }
288 
289  // extra immobile movement animation for take-off
290  animator_.add_animation(temp_unit_ptr_.get_unit_ptr(), "pre_movement", path_[0], path_[1]);
293  animator_.clear();
294 
295  // Switch the display back to the real unit.
296  u->set_facing(temp_unit_ptr_->facing());
297  u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
298  u->set_hidden(was_hidden_);
299  temp_unit_ptr_->set_hidden(true);
300 }
301 
302 
303 /**
304  * Visually moves a unit from the last hex we drew to the one specified by
305  * @a path_index. If @a path_index points to an earlier hex, we do nothing.
306  * The moving unit will only be updated if update is set to true; otherwise,
307  * the provided unit is merely hidden during the movement and re-shown after.
308  * (Not updating the unit can produce smoother animations in some cases.)
309  * If @a wait is set to false, this returns without waiting for the final
310  * animation to finish. Call wait_for_anims() to explicitly get this final
311  * wait (another call to proceed_to() or finish() will implicitly wait). The
312  * unit must remain valid until the wait is finished.
313  */
314 void unit_mover::proceed_to(unit_ptr u, std::size_t path_index, bool update, bool wait)
315 {
316  // Nothing to do here if animations cannot be shown.
317  if ( !can_draw_ || !animate_ )
318  return;
319 
320  // Handle pending visibility issues before introducing new ones.
321  wait_for_anims();
322 
323  if ( update || !temp_unit_ptr_ )
324  // Replace the temp unit (which also hides u and shows our temporary).
326  else
327  {
328  // Just switch the display from the real unit to our fake one.
329  temp_unit_ptr_->set_hidden(false);
330  u->set_hidden(true);
331  }
332 
333  // Safety check.
334  path_index = std::min(path_index, path_.size()-1);
335 
336  for ( ; current_ < path_index; ++current_ ) {
337  // It is possible for path_[current_] and path_[current_+1] not to be adjacent.
338  // When that is the case, and the unit is invisible at path_[current_], we shouldn't
339  // scroll to that hex.
340  std::vector<map_location> locs;
341  if (!temp_unit_ptr_->invisible(path_[current_]))
342  locs.push_back(path_[current_]);
343  if (!temp_unit_ptr_->invisible(path_[current_+1]))
344  locs.push_back(path_[current_+1]);
345  // If the unit can be seen by the viewing side while making this step:
346  if ( !is_enemy_ || !locs.empty() )
347  {
348  // Wait for the previous step to complete before drawing the next one.
349  wait_for_anims();
350 
353  {
354  // prevent the unit from disappearing if we scroll here with i == 0
355  temp_unit_ptr_->set_location(path_[current_]);
357  // scroll in as much of the remaining path as possible
358  if ( temp_unit_ptr_->anim_comp().get_animation() )
359  temp_unit_ptr_->anim_comp().get_animation()->pause_animation();
361  true, false, 0.0, force_scroll_);
362  if ( temp_unit_ptr_->anim_comp().get_animation() )
363  temp_unit_ptr_->anim_comp().get_animation()->restart_animation();
364  }
365 
367  wait_until_ =
368  move_unit_between(path_[current_], path_[current_+1],
370  path_.size() - (current_+2), animator_,
371  *disp_);
372  else if ( path_[current_] != path_[current_+1] )
373  teleport_unit_between(path_[current_], path_[current_+1],
374  *temp_unit_ptr_, *disp_);
375  }
376  }
377 
378  // Update the unit's facing.
379  u->set_facing(temp_unit_ptr_->facing());
380  u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
381  // Remember the unit to unhide when the animation finishes.
382  shown_unit_ = u;
383  if ( wait )
384  wait_for_anims();
385 }
386 
387 
388 /**
389  * Waits for the final animation of the most recent proceed_to() to finish.
390  * It is not necessary to call this unless you want to wait before the next
391  * call to proceed_to() or finish().
392  */
394 {
395  if ( wait_until_ == std::chrono::milliseconds::max() )
396  // Wait for end (not currently used, but still supported).
398  else if ( wait_until_ != std::chrono::milliseconds::min() ) {
399  // Wait until the specified time (used for normal movement).
401  // debug code, see unit_frame::redraw()
402  // std::cout << " end\n";
403  // TODO: For wesnoth 1.14+: check if efficient for redrawing?
404  // Check with large animated units too make sure artifacts are
405  // not left on screen after unit movement in particular.
406  if ( disp_ ) { // Should always be true if we get here.
407  // Invalidate the hexes around the move that prompted this wait.
408  for(const map_location& adj : get_adjacent_tiles(path_[current_ - 1])) {
409  disp_->invalidate(adj);
410  }
411 
412  for(const map_location& adj : get_adjacent_tiles(path_[current_])) {
413  disp_->invalidate(adj);
414  }
415  }
416  }
417 
418  // Reset data.
419  wait_until_ = std::chrono::milliseconds::min();
420  animator_.clear();
421 
423 }
424 
425 
426 /**
427  * Finishes the display of movement for the supplied unit.
428  * If called before showing the unit reach the end of the path, it will be
429  * assumed that the movement ended early.
430  * If @a dir is not supplied, the final direction will be determined by (the
431  * last two traversed hexes of) the path.
432  */
434 {
435  // Nothing to do here if the display is not valid.
436  if ( !can_draw_ ) {
437  // Make sure to reset the unit's animation to deal with a quirk in the
438  // action engine where it leaves it to us to reenable bars even if the
439  // display is initially locked.
440  u->anim_comp().set_standing(true);
441  return;
442  }
443 
444  const map_location & end_loc = path_[current_];
445  const map_location::direction final_dir = current_ == 0 ?
446  path_[0].get_relative_dir(path_[1]) :
447  path_[current_-1].get_relative_dir(end_loc);
448 
449  if ( animate_ )
450  {
451  wait_for_anims(); // In case proceed_to() did not wait for the last animation.
452 
453  // Make sure the displayed unit is correct.
455  temp_unit_ptr_->set_location(end_loc);
456  temp_unit_ptr_->set_facing(final_dir);
457 
458  // Animation
459  animator_.add_animation(temp_unit_ptr_.get_unit_ptr(), "post_movement", end_loc);
462  animator_.clear();
463 
464  // Switch the display back to the real unit.
465  u->set_hidden(was_hidden_);
466  temp_unit_ptr_->set_hidden(true);
467 
469  mousehandler->invalidate_reachmap();
470  }
471  }
472  else
473  {
474  // Show the unit at end of skipped animation
475  u->set_hidden(was_hidden_);
476  }
477 
478  // Facing gets set even when not animating.
479  u->set_facing(dir == map_location::direction::indeterminate ? final_dir : dir);
480  u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
481 
482  // Redraw path ends (even if not animating).
483  disp_->invalidate(path_.front());
484  disp_->invalidate(end_loc);
485 }
486 
487 
488 /**
489  * Display a unit moving along a given path.
490  *
491  * @param path The path to traverse.
492  * @param u The unit to show being moved. Its facing will be updated,
493  * but not its position.
494  * @param animate If set to false, only side-effects of move are applied
495  * (correct unit facing, path hexes redrawing).
496  * @param dir Unit will be set facing this direction after move.
497  * If nothing passed, direction will be set based on path.
498  * @param force_scroll
499  */
500 /* Note: Hide the unit in its current location,
501  * but don't actually remove it until the move is done,
502  * so that while the unit is moving status etc.
503  * will still display the correct number of units.
504  */
505 void move_unit(const std::vector<map_location>& path, unit_ptr u,
506  bool animate, map_location::direction dir,
507  bool force_scroll)
508 {
509  unit_mover mover(path, animate, force_scroll);
510 
511  mover.start(u);
512  mover.proceed_to(u, path.size());
513  mover.finish(u, dir);
514 }
515 
516 
517 void reset_helpers(const unit *attacker,const unit *defender);
518 
519 void unit_draw_weapon(const map_location& loc, unit& attacker,
520  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& defender_loc, unit_ptr defender)
521 {
523  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
524  return;
525  }
526  unit_animator animator;
527  attacker.set_facing(loc.get_relative_dir(defender_loc));
528  defender->set_facing(defender_loc.get_relative_dir(loc));
529  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);
530  if(defender) {
531  animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},strike_result::type::miss,secondary_attack,attack,0);
532  }
533  animator.start_animations();
534  animator.wait_for_end();
535 
536 }
537 
538 
539 void unit_sheath_weapon(const map_location& primary_loc, unit_ptr primary_unit,
540  const_attack_ptr primary_attack,const_attack_ptr secondary_attack, const map_location& secondary_loc,unit_ptr secondary_unit)
541 {
543  if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !prefs::get().show_combat()) {
544  return;
545  }
546  unit_animator animator;
547  if(primary_unit) {
548  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);
549  }
550  if(secondary_unit) {
551  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);
552  }
553 
554  if(primary_unit || secondary_unit) {
555  animator.start_animations();
556  animator.wait_for_end();
557  }
558  if(primary_unit) {
559  primary_unit->anim_comp().set_standing();
560  }
561  if(secondary_unit) {
562  secondary_unit->anim_comp().set_standing();
563  }
564  reset_helpers(primary_unit.get(),secondary_unit.get());
565 
566 }
567 
568 
569 void unit_die(const map_location& loc, unit& loser,
570  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& winner_loc, unit_ptr winner)
571 {
573  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
574  return;
575  }
576  unit_animator animator;
577  // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
578  animator.add_animation(loser.shared_from_this(),"death",loc,winner_loc,0,false,"",{0,0,0},strike_result::type::kill,attack,secondary_attack,0);
579  // but show the bars of the winner (avoid blinking and show its xp gain)
580  if(winner) {
581  animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
582  strike_result::type::kill,secondary_attack,attack,0);
583  }
584  animator.start_animations();
585  animator.wait_for_end();
586 
587  reset_helpers(winner.get(), &loser);
588 
590  mousehandler->invalidate_reachmap();
591  }
592 }
593 
594 
595 void unit_attack(display * disp, game_board & board,
596  const map_location& a, const map_location& b, int damage,
597  const attack_type& attack, const_attack_ptr secondary_attack,
598  int swing, const std::string& hit_text, int drain_amount,
599  const std::string& att_text,
600  const std::vector<std::string>* extra_hit_sounds,
601  bool attacking)
602 {
603  if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !prefs::get().show_combat()) {
604  return;
605  }
606  //const unit_map& units = disp->get_units();
608 
609  // scroll such that there is at least half a hex spacing around fighters
610  disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
611 
612  log_scope("unit_attack");
613 
614  const unit_map::const_iterator att = board.units().find(a);
615  assert(att.valid());
616  const unit& attacker = *att;
617 
618  const unit_map::iterator def = board.find_unit(b);
619  assert(def.valid());
620  unit &defender = *def;
621  int def_hitpoints = defender.hitpoints();
622  const_attack_ptr weapon = attack.shared_from_this();
623  auto ctx = weapon->specials_context(attacker.shared_from_this(), defender.shared_from_this(), a, b, attacking, secondary_attack);
624  utils::optional<decltype(ctx)> opp_ctx;
625 
626  if(secondary_attack) {
627  opp_ctx.emplace(secondary_attack->specials_context(defender.shared_from_this(), attacker.shared_from_this(), b, a, !attacking, weapon));
628  }
629 
630  att->set_facing(a.get_relative_dir(b));
631  def->set_facing(b.get_relative_dir(a));
632  defender.set_facing(b.get_relative_dir(a));
633 
634  std::string text = number_and_text(damage, hit_text);
635  std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
636 
637  strike_result::type hit_type;
638  if(damage >= defender.hitpoints()) {
639  hit_type = strike_result::type::kill;
640  } else if(damage > 0) {
641  hit_type = strike_result::type::hit;
642  }else {
643  hit_type = strike_result::type::miss;
644  }
645 
646  unit_animator animator;
647 
648  animator.add_animation(attacker.shared_from_this(), "attack", att->get_location(), def->get_location(), damage, true, text_2,
649  (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, weapon,
650  secondary_attack, swing);
651 
652  // note that we take an anim from the real unit, we'll use it later
653  const unit_animation* defender_anim = def->anim_comp().choose_animation(def->get_location(), "defend",
654  att->get_location(), damage, hit_type, weapon, secondary_attack, swing);
655 
656  animator.add_animation(defender.shared_from_this(), defender_anim, def->get_location(), true, text, {255, 0, 0});
657 
658  unit_ability_list leadership_list = attacker.get_abilities_weapons("leadership", weapon, secondary_attack);
659  unit_ability_list resistance_list = defender.get_abilities_weapons("resistance", secondary_attack, weapon);
660  for(const unit_ability& ability : leadership_list) {
661  if(ability.teacher_loc == a) {
662  continue;
663  }
664 
665  if(ability.teacher_loc == b) {
666  continue;
667  }
668 
669  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
670  assert(leader.valid());
671  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
672  leader->anim_comp().invalidate(*disp);
673  animator.add_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
674  att->get_location(), damage, true, "", {0,0,0},
675  hit_type, weapon, secondary_attack, swing);
676  }
677 
678  for(const unit_ability& ability : resistance_list) {
679  if(ability.teacher_loc == a) {
680  continue;
681  }
682 
683  if(ability.teacher_loc == b) {
684  continue;
685  }
686 
687  unit_map::const_iterator helper = board.units().find(ability.teacher_loc);
688  assert(helper.valid());
689  helper->set_facing(ability.teacher_loc.get_relative_dir(b));
690  animator.add_animation(helper.get_shared_ptr(), "resistance", ability.teacher_loc,
691  def->get_location(), damage, true, "", {0,0,0},
692  hit_type, weapon, secondary_attack, swing);
693  }
694 
695  unit_ability_list abilities = att->get_location();
696  for(auto& special : attacker.checking_tags()) {
697  abilities.append(weapon->get_weapon_ability(special));
698  }
699 
700  for(const unit_ability& ability : abilities) {
701  if(ability.teacher_loc == a) {
702  continue;
703  }
704 
705  if(ability.teacher_loc == b) {
706  continue;
707  }
708 
709  bool leading_playable = false;
710  bool helping_playable = false;
711  for(const unit_ability& leader_list : leadership_list) {
712  if(ability.teacher_loc == leader_list.teacher_loc) {
713  leading_playable = true;
714  break;
715  }
716  }
717 
718  for(const unit_ability& helper_list : resistance_list) {
719  if(ability.teacher_loc == helper_list.teacher_loc) {
720  helping_playable = true;
721  break;
722  }
723  }
724 
725  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
726  assert(leader.valid());
727  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
728  if(animator.has_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
729  att->get_location(), damage, hit_type, weapon, secondary_attack, swing) && leading_playable){
730  continue;
731  }
732  if(animator.has_animation(leader.get_shared_ptr(), "resistance", ability.teacher_loc,
733  def->get_location(), damage, hit_type, weapon, secondary_attack, swing) && helping_playable){
734  continue;
735  }
736  animator.add_animation(leader.get_shared_ptr(), "teaching", ability.teacher_loc,
737  att->get_location(), damage, true, "", {0,0,0},
738  hit_type, weapon, secondary_attack, swing);
739  }
740 
741 
742  animator.start_animations();
743  animator.wait_until(0ms);
744  int damage_left = damage;
745  bool extra_hit_sounds_played = false;
746  while(damage_left > 0 && !animator.would_end()) {
747  if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
748  for (std::string hit_sound : *extra_hit_sounds) {
749  sound::play_sound(hit_sound);
750  }
751  extra_hit_sounds_played = true;
752  }
753 
754  auto step_left = (animator.get_end_time() - animator.get_animation_time() ) / 50ms;
755  if(step_left < 1) step_left = 1;
756  int removed_hp = damage_left/step_left ;
757  if(removed_hp < 1) removed_hp = 1;
758  defender.take_hit(removed_hp);
759  damage_left -= removed_hp;
760  animator.wait_until(animator.get_animation_time_potential() + 50ms);
761  }
762  animator.wait_for_end();
763  // pass the animation back to the real unit
764  def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
765  reset_helpers(&*att, &*def);
766  def->set_hitpoints(def_hitpoints);
767 }
768 
769 // private helper function, set all helpers to default position
770 void reset_helpers(const unit *attacker,const unit *defender)
771 {
773  const unit_map& units = disp->context().units();
774  if(attacker) {
775  unit_ability_list attacker_abilities = attacker->get_abilities("leadership");
776  for(auto& special : attacker->checking_tags()) {
777  attacker_abilities.append(attacker->get_abilities(special));
778  }
779  for(const unit_ability& ability : attacker_abilities) {
780  unit_map::const_iterator leader = units.find(ability.teacher_loc);
781  assert(leader != units.end());
782  leader->anim_comp().set_standing();
783  }
784  }
785 
786  if(defender) {
787  unit_ability_list defender_abilities = defender->get_abilities("resistance");
788  for(auto& special : defender->checking_tags()) {
789  defender_abilities.append(defender->get_abilities(special));
790  }
791  for(const unit_ability& ability : defender_abilities) {
792  unit_map::const_iterator helper = units.find(ability.teacher_loc);
793  assert(helper != units.end());
794  helper->anim_comp().set_standing();
795  }
796  }
797 }
798 
799 void unit_recruited(const map_location& loc,const map_location& leader_loc)
800 {
802  if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
803  return;
804  }
805 
806  const team& viewing_team = disp->viewing_team();
807  const unit_map& units = disp->context().units();
808 
809  unit_map::const_iterator u = units.find(loc);
810  if(u == units.end()) return;
811  const bool unit_visible = u->is_visible_to_team(viewing_team, false);
812 
813  unit_map::const_iterator leader = units.find(leader_loc); // may be null_location
814  const bool leader_visible = (leader != units.end()) && leader->is_visible_to_team(viewing_team, false);
815 
816  unit_animator animator;
817 
818  {
819  ON_SCOPE_EXIT(u) {
820  u->set_hidden(false);
821  };
822  u->set_hidden(true);
823 
824  if (leader_visible && unit_visible) {
825  disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
826  } else if (leader_visible) {
827  disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
828  } else if (unit_visible) {
829  disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
830  } else {
831  return;
832  }
833  if(leader != units.end()) {
834  leader->set_facing(leader_loc.get_relative_dir(loc));
835  if (leader_visible) {
836  animator.add_animation(leader.get_shared_ptr(), "recruiting", leader_loc, loc, 0, true);
837  }
838  }
839  }
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
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:97
const team & viewing_team() const
Definition: display.cpp:342
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3087
@ ONSCREEN
Definition: display.hpp:509
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:1975
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:674
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:1883
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:1985
const display_context & context() const
Definition: display.hpp:193
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
const map_location & mouseover_hex() const
Definition: display.hpp:310
virtual void select_hex(map_location hex)
Definition: display.cpp:1476
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:172
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:175
bool is_enemy(int n) const
Definition: team.hpp:229
void append(const unit_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
Definition: unit.hpp:106
void set_standing(bool with_bars=true)
Sets the animation state to standing.
void replace_anim_if_invalid(unit_const_ptr animated_unit, const std::string &event, const map_location &src=map_location::null_location(), const map_location &dst=map_location::null_location(), const int value=0, bool with_bars=false, const std::string &text="", const color_t text_color={0, 0, 0}, const strike_result::type hit_type=strike_result::type::invalid, const_attack_ptr attack=nullptr, const_attack_ptr second_attack=nullptr, int value2=0)
Definition: animation.cpp:1345
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(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_attack_ptr attack=nullptr, const_attack_ptr second_attack=nullptr, int value2=0) const
has_animation : return an boolean value if animated unit present and have animation specified,...
Definition: animation.cpp:1332
void wait_for_end() const
Definition: animation.cpp:1437
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 wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:393
void start(unit_ptr u)
Initiates the display of movement for the supplied unit.
Definition: udisplay.cpp:257
void finish(unit_ptr u, map_location::direction dir=map_location::direction::indeterminate)
Finishes the display of movement for the supplied unit.
Definition: udisplay.cpp:433
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
void replace_temporary(unit_ptr u)
Makes the temporary unit used by this match the supplied unit.
Definition: udisplay.cpp:216
game_display *const disp_
Definition: udisplay.hpp:63
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition: udisplay.cpp:242
std::chrono::milliseconds wait_until_
The animation potential to wait until.
Definition: udisplay.hpp:69
void proceed_to(unit_ptr u, std::size_t path_index, bool update=false, bool wait=true)
Visually moves a unit from the last hex we drew to the one specified by path_index.
Definition: udisplay.cpp:314
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:253
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition: unit.cpp:2622
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:216
const std::set< std::string > & checking_tags() const
Definition: unit.hpp:1809
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:499
bool take_hit(int damage)
Damage the unit.
Definition: unit.hpp:816
unit_animation_component & anim_comp() const
Definition: unit.hpp:1614
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1404
void set_facing(map_location::direction dir) const
The this unit's facing.
Definition: unit.cpp:1681
void set_location(const map_location &loc)
Sets this unit's map location.
Definition: unit.hpp:1414
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:278
static void update()
void pump()
Process all events currently in the queue.
Definition: events.cpp:479
std::string path
Definition: filesystem.cpp:90
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:1046
Contains a number of free functions which display units.
Definition: udisplay.cpp:38
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:799
void unit_draw_weapon(const map_location &loc, unit &attacker, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &defender_loc, unit_ptr defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
Definition: udisplay.cpp:519
void unit_die(const map_location &loc, unit &loser, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &winner_loc, unit_ptr winner)
Show a unit fading out.
Definition: udisplay.cpp:569
void unit_sheath_weapon(const map_location &primary_loc, unit_ptr primary_unit, const_attack_ptr primary_attack, const_attack_ptr secondary_attack, const map_location &secondary_loc, unit_ptr secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation.
Definition: udisplay.cpp:539
void reset_helpers(const unit *attacker, const unit *defender)
Definition: udisplay.cpp:770
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const_attack_ptr secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds, bool attacking)
Make the unit on tile 'a' attack the unit on tile 'b'.
Definition: udisplay.cpp:595
void move_unit(const std::vector< map_location > &path, unit_ptr u, bool animate, map_location::direction dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:505
std::size_t size(const std::string &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:59
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