The Battle for Wesnoth  1.19.1+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 display_context& dc = disp.get_disp_context();
77  const team& viewing_team = dc.get_team(disp.viewing_side());
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  }
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  }
109 
110  temp_unit.anim_comp().set_standing();
111  events::pump();
112 }
113 
114 /**
115  * Animates a single step between hexes.
116  * This will return before the animation actually finishes, allowing other
117  * processing to occur during the animation.
118  *
119  * @param a The starting hex.
120  * @param b The ending hex.
121  * @param temp_unit The unit to animate (historically, a temporary unit).
122  * @param step_num The number of steps taken so far (used to pick an animation).
123  * @param step_left The number of steps remaining (used to pick an animation).
124  * @param animator The unit_animator to use. This is assumed clear when we start,
125  * but will likely not be clear when we return.
126  * @param disp The game display. Assumed neither locked nor faked.
127  * @returns The animation potential until this animation will finish.
128  * INT_MIN indicates that no animation is pending.
129  */
130 int move_unit_between(const map_location& a,
131  const map_location& b,
132  unit_ptr temp_unit,
133  unsigned int step_num,
134  unsigned int step_left,
135  unit_animator& animator,
136  display& disp)
137 {
138  if ( disp.fogged(a) && disp.fogged(b) ) {
139  return std::numeric_limits<int>::min();
140  }
141 
142  temp_unit->set_location(a);
143  disp.invalidate(a);
144  temp_unit->set_facing(a.get_relative_dir(b));
145  animator.replace_anim_if_invalid(temp_unit,"movement",a,b,step_num,
146  false,"",{0,0,0},strike_result::type::invalid,nullptr,nullptr,step_left);
147  animator.start_animations();
148  animator.pause_animation();
149  disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
150  animator.restart_animation();
151 
152  // useless now, previous short draw() just did one
153  // new_animation_frame();
154 
155  int target_time = animator.get_animation_time_potential();
156  // target_time must be short to avoid jumpy move
157  // std::cout << "target time: " << target_time << "\n";
158  // we round it to the next multiple of 200 so that movement aligns to hex changes properly
159  target_time += 200;
160  target_time -= target_time%200;
161 
162  return target_time;
163 }
164 
165 bool do_not_show_anims(display* disp)
166 {
168 }
169 
170 } // end anon namespace
171 
172 /**
173  * The path must remain unchanged for the life of this object.
174  */
175 unit_mover::unit_mover(const std::vector<map_location>& path, bool animate, bool force_scroll) :
176  disp_(game_display::get_singleton()),
177  can_draw_(disp_ && !video::headless() && path.size() > 1),
178  animate_(animate),
179  force_scroll_(force_scroll),
180  animator_(),
181  wait_until_(std::numeric_limits<int>::min()),
182  shown_unit_(),
183  path_(path),
184  current_(0),
185  temp_unit_ptr_(),
186  // Somewhat arbitrary default values.
187  was_hidden_(false),
188  is_enemy_(true)
189 {
190  // Some error conditions that indicate something has gone very wrong.
191  // (This class can handle these conditions, but someone wanted them
192  // to be assertions.)
193  assert(!path_.empty());
194  assert(disp_);
195 }
196 
197 
199 {
200  // Make sure a unit hidden for movement is unhidden.
202  // For safety, clear the animator before deleting the temp unit.
203  animator_.clear();
204 }
205 
206 
207 /**
208  * Makes the temporary unit used by this match the supplied unit.
209  * This is called when setting the initial unit, as well as replacing it with
210  * something new.
211  * When this finishes, the supplied unit is hidden, while the temporary unit
212  * is not hidden.
213  */
214 /* Note: Hide the unit in its current location; do not actually remove it.
215  * Otherwise the status displays will be wrong during the movement.
216  */
218 {
219  if ( disp_ == nullptr )
220  // No point in creating a temp unit with no way to display it.
221  return;
222 
223  // Save the hidden state of the unit.
224  was_hidden_ = u->get_hidden();
225 
226  // Make our temporary unit mostly match u...
228 
229  // ... but keep the temporary unhidden and hide the original.
230  temp_unit_ptr_->set_hidden(false);
231  u->set_hidden(true);
232 
233  // Update cached data.
235 }
236 
237 
238 /**
239  * Switches the display back to *shown_unit_ after animating.
240  * This uses temp_unit_ptr_, so (in the destructor) call this before deleting
241  * temp_unit_ptr_.
242  */
244 {
245  if ( shown_unit_ ) {
246  // Switch the display back to the real unit.
247  shown_unit_->set_hidden(was_hidden_);
248  temp_unit_ptr_->set_hidden(true);
249  shown_unit_.reset();
250  }
251 }
252 
253 
254 /**
255  * Initiates the display of movement for the supplied unit.
256  * This should be called before attempting to display moving to a new hex.
257  */
259 {
260  // Nothing to do here if there is nothing to animate.
261  if ( !can_draw_ )
262  return;
263  // If no animation then hide unit until end of movement
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_mover::proceed_to(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()->restart_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::numeric_limits<int>::max() )
397  // Wait for end (not currently used, but still supported).
399  else if ( wait_until_ != std::numeric_limits<int>::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::numeric_limits<int>::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 
470  mousehandler->invalidate_reachmap();
471  }
472  }
473  else
474  {
475  // Show the unit at end of skipped animation
476  u->set_hidden(was_hidden_);
477  }
478 
479  // Facing gets set even when not animating.
480  u->set_facing(dir == map_location::NDIRECTIONS ? final_dir : dir);
481  u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
482 
483  // Redraw path ends (even if not animating).
484  disp_->invalidate(path_.front());
485  disp_->invalidate(end_loc);
486 }
487 
488 
489 /**
490  * Display a unit moving along a given path.
491  *
492  * @param path The path to traverse.
493  * @param u The unit to show being moved. Its facing will be updated,
494  * but not its position.
495  * @param animate If set to false, only side-effects of move are applied
496  * (correct unit facing, path hexes redrawing).
497  * @param dir Unit will be set facing this direction after move.
498  * If nothing passed, direction will be set based on path.
499  * @param force_scroll
500  */
501 /* Note: Hide the unit in its current location,
502  * but don't actually remove it until the move is done,
503  * so that while the unit is moving status etc.
504  * will still display the correct number of units.
505  */
506 void move_unit(const std::vector<map_location>& path, unit_ptr u,
507  bool animate, map_location::DIRECTION dir,
508  bool force_scroll)
509 {
510  unit_mover mover(path, animate, force_scroll);
511 
512  mover.start(u);
513  mover.proceed_to(u, path.size());
514  mover.finish(u, dir);
515 }
516 
517 
518 void reset_helpers(const unit *attacker,const unit *defender);
519 
520 void unit_draw_weapon(const map_location& loc, unit& attacker,
521  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& defender_loc, unit_ptr defender)
522 {
524  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
525  return;
526  }
527  unit_animator animator;
528  attacker.set_facing(loc.get_relative_dir(defender_loc));
529  defender->set_facing(defender_loc.get_relative_dir(loc));
530  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);
531  if(defender) {
532  animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},strike_result::type::miss,secondary_attack,attack,0);
533  }
534  animator.start_animations();
535  animator.wait_for_end();
536 
537 }
538 
539 
540 void unit_sheath_weapon(const map_location& primary_loc, unit_ptr primary_unit,
541  const_attack_ptr primary_attack,const_attack_ptr secondary_attack, const map_location& secondary_loc,unit_ptr secondary_unit)
542 {
544  if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !prefs::get().show_combat()) {
545  return;
546  }
547  unit_animator animator;
548  if(primary_unit) {
549  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);
550  }
551  if(secondary_unit) {
552  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);
553  }
554 
555  if(primary_unit || secondary_unit) {
556  animator.start_animations();
557  animator.wait_for_end();
558  }
559  if(primary_unit) {
560  primary_unit->anim_comp().set_standing();
561  }
562  if(secondary_unit) {
563  secondary_unit->anim_comp().set_standing();
564  }
565  reset_helpers(primary_unit.get(),secondary_unit.get());
566 
567 }
568 
569 
570 void unit_die(const map_location& loc, unit& loser,
571  const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& winner_loc, unit_ptr winner)
572 {
574  if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
575  return;
576  }
577  unit_animator animator;
578  // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
579  animator.add_animation(loser.shared_from_this(),"death",loc,winner_loc,0,false,"",{0,0,0},strike_result::type::kill,attack,secondary_attack,0);
580  // but show the bars of the winner (avoid blinking and show its xp gain)
581  if(winner) {
582  animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
583  strike_result::type::kill,secondary_attack,attack,0);
584  }
585  animator.start_animations();
586  animator.wait_for_end();
587 
588  reset_helpers(winner.get(), &loser);
589 
591  mousehandler->invalidate_reachmap();
592  }
593 }
594 
595 
596 void unit_attack(display * disp, game_board & board,
597  const map_location& a, const map_location& b, int damage,
598  const attack_type& attack, const_attack_ptr secondary_attack,
599  int swing, const std::string& hit_text, int drain_amount,
600  const std::string& att_text,
601  const std::vector<std::string>* extra_hit_sounds,
602  bool attacking)
603 {
604  if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !prefs::get().show_combat()) {
605  return;
606  }
607  //const unit_map& units = disp->get_units();
609 
610  // scroll such that there is at least half a hex spacing around fighters
611  disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
612 
613  log_scope("unit_attack");
614 
615  const unit_map::const_iterator att = board.units().find(a);
616  assert(att.valid());
617  const unit& attacker = *att;
618 
619  const unit_map::iterator def = board.find_unit(b);
620  assert(def.valid());
621  unit &defender = *def;
622  int def_hitpoints = defender.hitpoints();
623  const_attack_ptr weapon = attack.shared_from_this();
624  auto ctx = weapon->specials_context(attacker.shared_from_this(), defender.shared_from_this(), a, b, attacking, secondary_attack);
625  std::optional<decltype(ctx)> opp_ctx;
626 
627  if(secondary_attack) {
628  opp_ctx.emplace(secondary_attack->specials_context(defender.shared_from_this(), attacker.shared_from_this(), b, a, !attacking, weapon));
629  }
630 
631  att->set_facing(a.get_relative_dir(b));
632  def->set_facing(b.get_relative_dir(a));
633  defender.set_facing(b.get_relative_dir(a));
634 
635  std::string text = number_and_text(damage, hit_text);
636  std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
637 
638  strike_result::type hit_type;
639  if(damage >= defender.hitpoints()) {
640  hit_type = strike_result::type::kill;
641  } else if(damage > 0) {
642  hit_type = strike_result::type::hit;
643  }else {
644  hit_type = strike_result::type::miss;
645  }
646 
647  unit_animator animator;
648 
649  animator.add_animation(attacker.shared_from_this(), "attack", att->get_location(), def->get_location(), damage, true, text_2,
650  (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, weapon,
651  secondary_attack, swing);
652 
653  // note that we take an anim from the real unit, we'll use it later
654  const unit_animation* defender_anim = def->anim_comp().choose_animation(def->get_location(), "defend",
655  att->get_location(), damage, hit_type, weapon, secondary_attack, swing);
656 
657  animator.add_animation(defender.shared_from_this(), defender_anim, def->get_location(), true, text, {255, 0, 0});
658 
659  unit_ability_list leadership_list = attacker.get_abilities_weapons("leadership", weapon, secondary_attack);
660  unit_ability_list resistance_list = defender.get_abilities_weapons("resistance", secondary_attack, weapon);
661  for(const unit_ability& ability : leadership_list) {
662  if(ability.teacher_loc == a) {
663  continue;
664  }
665 
666  if(ability.teacher_loc == b) {
667  continue;
668  }
669 
670  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
671  assert(leader.valid());
672  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
673  leader->anim_comp().invalidate(*disp);
674  animator.add_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
675  att->get_location(), damage, true, "", {0,0,0},
676  hit_type, weapon, secondary_attack, swing);
677  }
678 
679  for(const unit_ability& ability : resistance_list) {
680  if(ability.teacher_loc == a) {
681  continue;
682  }
683 
684  if(ability.teacher_loc == b) {
685  continue;
686  }
687 
688  unit_map::const_iterator helper = board.units().find(ability.teacher_loc);
689  assert(helper.valid());
690  helper->set_facing(ability.teacher_loc.get_relative_dir(b));
691  animator.add_animation(helper.get_shared_ptr(), "resistance", ability.teacher_loc,
692  def->get_location(), damage, true, "", {0,0,0},
693  hit_type, weapon, secondary_attack, swing);
694  }
695 
696  unit_ability_list abilities = att->get_location();
697  for(auto& special : attacker.checking_tags()) {
698  abilities.append(weapon->get_weapon_ability(special));
699  }
700 
701  for(const unit_ability& ability : abilities) {
702  if(ability.teacher_loc == a) {
703  continue;
704  }
705 
706  if(ability.teacher_loc == b) {
707  continue;
708  }
709 
710  bool leading_playable = false;
711  bool helping_playable = false;
712  for(const unit_ability& leader_list : leadership_list) {
713  if(ability.teacher_loc == leader_list.teacher_loc) {
714  leading_playable = true;
715  break;
716  }
717  }
718 
719  for(const unit_ability& helper_list : resistance_list) {
720  if(ability.teacher_loc == helper_list.teacher_loc) {
721  helping_playable = true;
722  break;
723  }
724  }
725 
726  unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
727  assert(leader.valid());
728  leader->set_facing(ability.teacher_loc.get_relative_dir(a));
729  if(animator.has_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
730  att->get_location(), damage, hit_type, weapon, secondary_attack, swing) && leading_playable){
731  continue;
732  }
733  if(animator.has_animation(leader.get_shared_ptr(), "resistance", ability.teacher_loc,
734  def->get_location(), damage, hit_type, weapon, secondary_attack, swing) && helping_playable){
735  continue;
736  }
737  animator.add_animation(leader.get_shared_ptr(), "teaching", ability.teacher_loc,
738  att->get_location(), damage, true, "", {0,0,0},
739  hit_type, weapon, secondary_attack, swing);
740  }
741 
742 
743  animator.start_animations();
744  animator.wait_until(0);
745  int damage_left = damage;
746  bool extra_hit_sounds_played = false;
747  while(damage_left > 0 && !animator.would_end()) {
748  if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
749  for (std::string hit_sound : *extra_hit_sounds) {
750  sound::play_sound(hit_sound);
751  }
752  extra_hit_sounds_played = true;
753  }
754 
755  int step_left = (animator.get_end_time() - animator.get_animation_time() )/50;
756  if(step_left < 1) step_left = 1;
757  int removed_hp = damage_left/step_left ;
758  if(removed_hp < 1) removed_hp = 1;
759  defender.take_hit(removed_hp);
760  damage_left -= removed_hp;
761  animator.wait_until(animator.get_animation_time_potential() +50);
762  }
763  animator.wait_for_end();
764  // pass the animation back to the real unit
765  def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
766  reset_helpers(&*att, &*def);
767  def->set_hitpoints(def_hitpoints);
768 }
769 
770 // private helper function, set all helpers to default position
771 void reset_helpers(const unit *attacker,const unit *defender)
772 {
774  const unit_map& units = disp->get_units();
775  if(attacker) {
776  unit_ability_list attacker_abilities = attacker->get_abilities("leadership");
777  for(auto& special : attacker->checking_tags()) {
778  attacker_abilities.append(attacker->get_abilities(special));
779  }
780  for(const unit_ability& ability : attacker_abilities) {
781  unit_map::const_iterator leader = units.find(ability.teacher_loc);
782  assert(leader != units.end());
783  leader->anim_comp().set_standing();
784  }
785  }
786 
787  if(defender) {
788  unit_ability_list defender_abilities = defender->get_abilities("resistance");
789  for(auto& special : defender->checking_tags()) {
790  defender_abilities.append(defender->get_abilities(special));
791  }
792  for(const unit_ability& ability : defender_abilities) {
793  unit_map::const_iterator helper = units.find(ability.teacher_loc);
794  assert(helper != units.end());
795  helper->anim_comp().set_standing();
796  }
797  }
798 }
799 
800 void unit_recruited(const map_location& loc,const map_location& leader_loc)
801 {
803  if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
804  return;
805  }
806 
807  const display_context& dc = disp->get_disp_context();
808  const team& viewing_team = dc.get_team(disp->viewing_side());
809 
810  unit_map::const_iterator u = disp->get_units().find(loc);
811  if(u == disp->get_units().end()) return;
812  const bool unit_visible = u->is_visible_to_team(viewing_team, false);
813 
814  unit_map::const_iterator leader = disp->get_units().find(leader_loc); // may be null_location
815  const bool leader_visible = (leader != disp->get_units().end()) && leader->is_visible_to_team(viewing_team, false);
816 
817  unit_animator animator;
818 
819  {
820  ON_SCOPE_EXIT(u) {
821  u->set_hidden(false);
822  };
823  u->set_hidden(true);
824 
825  if (leader_visible && unit_visible) {
826  disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
827  } else if (leader_visible) {
828  disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
829  } else if (unit_visible) {
830  disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
831  } else {
832  return;
833  }
834  if (leader != disp->get_units().end()) {
835  leader->set_facing(leader_loc.get_relative_dir(loc));
836  if (leader_visible) {
837  animator.add_animation(leader.get_shared_ptr(), "recruiting", leader_loc, loc, 0, true);
838  }
839  }
840  }
841  animator.add_animation(u.get_shared_ptr(), "recruited", loc, leader_loc);
842  animator.start_animations();
843  animator.wait_for_end();
844  animator.set_all_standing();
845  if (loc==disp->mouseover_hex()) disp->invalidate_unit();
846 }
847 
848 void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
849  const std::string & extra_text)
850 {
852  const map_location& healed_loc = healed.get_location();
853  const bool some_healer_is_unfogged =
854  (healers.end() != std::find_if_not(healers.begin(), healers.end(),
855  [&](unit* h) { return disp->fogged(h->get_location()); }));
856 
857  if(do_not_show_anims(disp) || (disp->fogged(healed_loc) && !some_healer_is_unfogged)) {
858  return;
859  }
860 
861  // This is all the pretty stuff.
862  disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
863  disp->display_unit_hex(healed_loc);
864  unit_animator animator;
865 
866  for (unit *h : healers) {
867  h->set_facing(h->get_location().get_relative_dir(healed_loc));
868  animator.add_animation(h->shared_from_this(), "healing", h->get_location(),
869  healed_loc, healing);
870  }
871 
872  if (healing < 0) {
873  animator.add_animation(healed.shared_from_this(), "poisoned", healed_loc,
874  map_location::null_location(), -healing, false,
875  number_and_text(-healing, extra_text),
876  {255,0,0});
877  } else if ( healing > 0 ) {
878  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
879  map_location::null_location(), healing, false,
880  number_and_text(healing, extra_text),
881  {0,255,0});
882  } else {
883  animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
884  map_location::null_location(), 0, false,
885  extra_text, {0,255,0});
886  }
887  animator.start_animations();
888  animator.wait_for_end();
889  animator.set_all_standing();
890 }
891 
892 } // 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
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
int viewing_side() const
The 1-based equivalent of the 0-based viewing_team() function.
Definition: display.hpp:131
const unit_map & get_units() const
Definition: display.hpp:149
const display_context & get_disp_context() const
Definition: display.hpp:190
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3145
@ ONSCREEN
Definition: display.hpp:506
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:2025
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:702
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:1926
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:2037
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:308
virtual void select_hex(map_location hex)
Definition: display.cpp:1504
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:46
team & get_team(int i)
Definition: game_board.hpp:91
unit_map::iterator find_unit(const map_location &loc)
Definition: game_board.hpp:171
virtual const unit_map & units() const override
Definition: game_board.hpp:106
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
bool is_skipping_replay() const
static prefs & get()
bool show_combat()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
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:1361
void wait_until(int animation_time) const
Definition: animation.cpp:1429
void pause_animation()
Definition: animation.cpp:1505
int get_animation_time() const
Definition: animation.cpp:1477
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:1348
void wait_for_end() const
Definition: animation.cpp:1460
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:1325
bool would_end() const
Definition: animation.cpp:1419
void start_animations()
Definition: animation.cpp:1395
void set_all_standing()
Definition: animation.cpp:1523
int get_animation_time_potential() const
Definition: animation.cpp:1485
void restart_animation()
Definition: animation.cpp:1514
int get_end_time() const
Definition: animation.cpp:1493
A class to encapsulate the steps of drawing a unit's move.
Definition: udisplay.hpp:45
const bool force_scroll_
Definition: udisplay.hpp:66
int wait_until_
The animation potential to wait until.
Definition: udisplay.hpp:69
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:394
void start(unit_ptr u)
Initiates the display of movement for the supplied unit.
Definition: udisplay.cpp:258
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 finish(unit_ptr u, map_location::DIRECTION dir=map_location::NDIRECTIONS)
Finishes the display of movement for the supplied unit.
Definition: udisplay.cpp:434
void replace_temporary(unit_ptr u)
Makes the temporary unit used by this match the supplied unit.
Definition: udisplay.cpp:217
game_display *const disp_
Definition: udisplay.hpp:63
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition: udisplay.cpp:243
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:315
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:2679
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:218
const std::set< std::string > & checking_tags() const
Definition: unit.hpp:1793
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:1599
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1389
void set_facing(map_location::DIRECTION dir) const
The this unit's facing.
Definition: unit.cpp:1761
void set_location(const map_location &loc)
Sets this unit's map location.
Definition: unit.hpp:1399
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:474
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:502
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:277
static void update()
void pump()
Process all events currently in the queue.
Definition: events.cpp:479
std::string path
Definition: filesystem.cpp:89
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:1033
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:848
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition: udisplay.cpp:800
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:520
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:506
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:570
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:540
void reset_helpers(const unit *attacker, const unit *defender)
Definition: udisplay.cpp:771
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:596
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:141
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:38
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:226
static const map_location & null_location()
Definition: location.hpp:81
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 a
#define b