The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
29#include "units/unit.hpp"
31#include "units/map.hpp"
32#include "utils/scope_exit.hpp"
33#include "video.hpp"
34
35#define LOG_DP LOG_STREAM(info, display)
36
37
38namespace unit_display
39{
40namespace
41{
42/**
43 * Returns a string whose first line is @a number, centered over a second line,
44 * which consists of @a text.
45 * If the number is 0, the first line is suppressed.
46 */
47std::string number_and_text(int number, const std::string& text)
48{
49 // Simple case.
50 if ( number == 0 )
51 return text;
52
53 std::ostringstream result;
54
55 if ( text.empty() )
56 result << number;
57 else
58 result << std::string((text.size()+1)/2, ' ') << number << '\n' << text;
59
60 return result.str();
61}
62
63
64/**
65 * Animates a teleportation between hexes.
66 *
67 * @param a The starting hex.
68 * @param b The ending hex.
69 * @param temp_unit The unit to animate (historically, a temporary unit).
70 * @param disp The game display. Assumed neither locked nor faked.
71 */
72void teleport_unit_between(const map_location& a, const map_location& b, unit& temp_unit, display& disp)
73{
74 if ( disp.fogged(a) && disp.fogged(b) ) {
75 return;
76 }
77 const team& viewing_team = disp.viewing_team();
78
79 const bool a_visible = temp_unit.is_visible_to_team(a, viewing_team, false);
80 const bool b_visible = temp_unit.is_visible_to_team(b, viewing_team, false);
81
82 temp_unit.set_location(a);
83 if ( a_visible ) { // teleport
84 disp.invalidate(a);
85 temp_unit.set_facing(a.get_relative_dir(b));
86 if ( b_visible )
87 disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
88 else
89 disp.scroll_to_tile(a, game_display::ONSCREEN, true, false);
90 unit_animator animator;
91 animator.add_animation(temp_unit.shared_from_this(),"pre_teleport",a);
92 animator.start_animations();
93 animator.wait_for_end();
94 temp_unit.anim_comp().reset_affect_adjacent(disp);
95 }
96
97 temp_unit.set_location(b);
98 if ( b_visible ) { // teleport
99 disp.invalidate(b);
100 temp_unit.set_facing(a.get_relative_dir(b));
101 if ( a_visible )
102 disp.scroll_to_tiles(b, a, game_display::ONSCREEN, true, 0.0, false);
103 else
104 disp.scroll_to_tile(b, game_display::ONSCREEN, true, false);
105 unit_animator animator;
106 animator.add_animation(temp_unit.shared_from_this(),"post_teleport",b);
107 animator.start_animations();
108 animator.wait_for_end();
109 temp_unit.anim_comp().reset_affect_adjacent(disp);
110 }
111
112 temp_unit.anim_comp().set_standing();
113 events::pump();
114}
115
116/**
117 * Animates a single step between hexes.
118 * This will return before the animation actually finishes, allowing other
119 * processing to occur during the animation.
120 *
121 * @param a The starting hex.
122 * @param b The ending hex.
123 * @param temp_unit The unit to animate (historically, a temporary unit).
124 * @param step_num The number of steps taken so far (used to pick an animation).
125 * @param step_left The number of steps remaining (used to pick an animation).
126 * @param animator The unit_animator to use. This is assumed clear when we start,
127 * but will likely not be clear when we return.
128 * @param disp The game display. Assumed neither locked nor faked.
129 * @returns The animation potential until this animation will finish.
130 * milliseconds::min indicates that no animation is pending.
131 */
132std::chrono::milliseconds move_unit_between(const map_location& a,
133 const map_location& b,
134 const unit_ptr& temp_unit,
135 unsigned int step_num,
136 unsigned int step_left,
137 unit_animator& animator,
138 display& disp)
139{
140 if ( disp.fogged(a) && disp.fogged(b) ) {
141 return std::chrono::milliseconds::min();
142 }
143
144 temp_unit->set_location(a);
145 disp.invalidate(a);
146 temp_unit->set_facing(a.get_relative_dir(b));
147 animator.replace_anim_if_invalid(temp_unit,"movement",a,b,step_num,
148 false,"",{0,0,0},strike_result::type::invalid,nullptr,nullptr,step_left);
149 animator.start_animations();
150 animator.pause_animation();
151 disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
152 animator.resume_animation();
153
154 auto target_time = animator.get_elapsed_time();
155 // target_time must be short to avoid jumpy move
156 // std::cout << "target time: " << target_time << "\n";
157 // we round it to the next multiple of 200 so that movement aligns to hex changes properly
158 target_time += 200ms;
159 target_time -= target_time % 200ms;
160
161 return target_time;
162}
163
164bool 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 */
174unit_movement_animator::unit_movement_animator(const std::vector<map_location>& path, bool animate, bool force_scroll) :
175 disp_(game_display::get_singleton()),
176 can_draw_(disp_ && !video::headless() && path.size() > 1),
177 animate_(animate),
178 force_scroll_(force_scroll),
179 animator_(),
180 wait_until_(std::chrono::milliseconds::min()),
181 shown_unit_(),
182 path_(path),
183 current_(0),
184 temp_unit_ptr_(),
185 // Somewhat arbitrary default values.
186 was_hidden_(false),
187 is_enemy_(true)
188{
189 // Some error conditions that indicate something has gone very wrong.
190 // (This class can handle these conditions, but someone wanted them
191 // to be assertions.)
192 assert(!path_.empty());
193 assert(disp_);
194}
195
196
198{
199 // Make sure a unit hidden for movement is unhidden.
201 // For safety, clear the animator before deleting the temp unit.
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);
249 }
250}
251
252
253/**
254 * Initiates the display of movement for the supplied unit.
255 * This should be called before attempting to display moving to a new hex.
256 */
258{
259 // Nothing to do here if there is nothing to animate.
260 if ( !can_draw_ )
261 return;
262 // If no animation then hide unit until end of movement
263 u->anim_comp().reset_affect_adjacent(*disp_);
264 if ( !animate_ ) {
265 was_hidden_ = u->get_hidden();
266 u->set_hidden(true);
267 return;
268 }
269
270 // This normally does nothing, but just in case...
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);
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
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 */
315void unit_movement_animator::proceed_to(const unit_ptr& u, std::size_t path_index, bool update, bool wait)
316{
317 // Nothing to do here if animations cannot be shown.
318 if ( !can_draw_ || !animate_ )
319 return;
320
321 // Handle pending visibility issues before introducing new ones.
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.
351
354 {
355 // prevent the unit from disappearing if we scroll here with i == 0
356 temp_unit_ptr_->set_location(path_[current_]);
358 // scroll in as much of the remaining path as possible
359 if ( temp_unit_ptr_->anim_comp().get_animation() )
360 temp_unit_ptr_->anim_comp().get_animation()->pause_animation();
362 true, false, 0.0, force_scroll_);
363 if ( temp_unit_ptr_->anim_comp().get_animation() )
364 temp_unit_ptr_->anim_comp().get_animation()->resume_animation();
365 }
366
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],
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 )
386}
387
388
389/**
390 * Waits for the final animation of the most recent proceed_to() to finish.
391 * It is not necessary to call this unless you want to wait before the next
392 * call to proceed_to() or finish().
393 */
395{
396 if ( wait_until_ == std::chrono::milliseconds::max() )
397 // Wait for end (not currently used, but still supported).
399 else if ( wait_until_ != std::chrono::milliseconds::min() ) {
400 // Wait until the specified time (used for normal movement).
402 // debug code, see unit_frame::redraw()
403 // std::cout << " end\n";
404 // TODO: For wesnoth 1.14+: check if efficient for redrawing?
405 // Check with large animated units too make sure artifacts are
406 // not left on screen after unit movement in particular.
407 if ( disp_ ) { // Should always be true if we get here.
408 // Invalidate the hexes around the move that prompted this wait.
409 for(const map_location& adj : get_adjacent_tiles(path_[current_ - 1])) {
410 disp_->invalidate(adj);
411 }
412
413 for(const map_location& adj : get_adjacent_tiles(path_[current_])) {
414 disp_->invalidate(adj);
415 }
416 }
417 }
418
419 // Reset data.
420 wait_until_ = std::chrono::milliseconds::min();
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 ?
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);
464
465 // Switch the display back to the real unit.
466 u->set_hidden(was_hidden_);
467 temp_unit_ptr_->set_hidden(true);
468 u->anim_comp().reset_affect_adjacent(*disp_);
469
471 mousehandler->invalidate_reachmap();
472 }
473 }
474 else
475 {
476 // Show the unit at end of skipped animation
477 u->set_hidden(was_hidden_);
478 }
479
480 // Facing gets set even when not animating.
481 u->set_facing(dir == map_location::direction::indeterminate ? final_dir : dir);
482 u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
483
484 // Redraw path ends (even if not animating).
485 disp_->invalidate(path_.front());
486 disp_->invalidate(end_loc);
487}
488
489
490/**
491 * Display a unit moving along a given path.
492 *
493 * @param path The path to traverse.
494 * @param u The unit to show being moved. Its facing will be updated,
495 * but not its position.
496 * @param animate If set to false, only side-effects of move are applied
497 * (correct unit facing, path hexes redrawing).
498 * @param dir Unit will be set facing this direction after move.
499 * If nothing passed, direction will be set based on path.
500 * @param force_scroll
501 */
502/* Note: Hide the unit in its current location,
503 * but don't actually remove it until the move is done,
504 * so that while the unit is moving status etc.
505 * will still display the correct number of units.
506 */
507void move_unit(const std::vector<map_location>& path, const unit_ptr& u,
508 bool animate, map_location::direction dir,
509 bool force_scroll)
510{
511 unit_movement_animator mover(path, animate, force_scroll);
512
513 mover.start(u);
514 mover.proceed_to(u, path.size());
515 mover.finish(u, dir);
516}
517
518
519void reset_helpers(const unit *attacker,const unit *defender);
520
521void unit_draw_weapon(const map_location& loc, unit& attacker,
522 const const_attack_ptr& attack,const const_attack_ptr& secondary_attack, const map_location& defender_loc, const unit_ptr& defender)
523{
525 if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
526 return;
527 }
528 unit_animator animator;
529 attacker.set_facing(loc.get_relative_dir(defender_loc));
530 defender->set_facing(defender_loc.get_relative_dir(loc));
531 animator.add_animation(attacker.shared_from_this(),"draw_weapon",loc,defender_loc,0,true,"",{0,0,0},strike_result::type::hit,attack,secondary_attack,0);
532 if(defender) {
533 animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},strike_result::type::miss,secondary_attack,attack,0);
534 }
535 animator.start_animations();
536 animator.wait_for_end();
537
538}
539
540
541void unit_sheath_weapon(const map_location& primary_loc, const unit_ptr& primary_unit,
542 const const_attack_ptr& primary_attack,const const_attack_ptr& secondary_attack, const map_location& secondary_loc,const unit_ptr& secondary_unit)
543{
545 if(do_not_show_anims(disp) || disp->fogged(primary_loc) || !prefs::get().show_combat()) {
546 return;
547 }
548 unit_animator animator;
549 if(primary_unit) {
550 animator.add_animation(primary_unit,"sheath_weapon",primary_loc,secondary_loc,0,true,"",{0,0,0},strike_result::type::invalid,primary_attack,secondary_attack,0);
551 }
552 if(secondary_unit) {
553 animator.add_animation(secondary_unit,"sheath_weapon",secondary_loc,primary_loc,0,true,"",{0,0,0},strike_result::type::invalid,secondary_attack,primary_attack,0);
554 }
555
556 if(primary_unit || secondary_unit) {
557 animator.start_animations();
558 animator.wait_for_end();
559 }
560 if(primary_unit) {
561 primary_unit->anim_comp().set_standing();
562 }
563 if(secondary_unit) {
564 secondary_unit->anim_comp().set_standing();
565 }
566 reset_helpers(primary_unit.get(),secondary_unit.get());
567
568}
569
570
571void unit_die(const map_location& loc, unit& loser,
572 const const_attack_ptr& attack,const const_attack_ptr& secondary_attack, const map_location& winner_loc, const unit_ptr& winner)
573{
575 if(do_not_show_anims(disp) || disp->fogged(loc) || !prefs::get().show_combat()) {
576 return;
577 }
578 unit_animator animator;
579 // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
580 animator.add_animation(loser.shared_from_this(),"death",loc,winner_loc,0,false,"",{0,0,0},strike_result::type::kill,attack,secondary_attack,0);
581 // but show the bars of the winner (avoid blinking and show its xp gain)
582 if(winner) {
583 animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
584 strike_result::type::kill,secondary_attack,attack,0);
585 }
586 animator.start_animations();
587 animator.wait_for_end();
588
589 reset_helpers(winner.get(), &loser);
590
592 mousehandler->invalidate_reachmap();
593 }
594 loser.anim_comp().reset_affect_adjacent(*disp);
595}
596
597
598void unit_attack(display * disp, game_board & board,
599 const map_location& a, const map_location& b, int damage,
600 const attack_type& attack, const const_attack_ptr& secondary_attack,
601 int swing, const std::string& hit_text, int drain_amount,
602 const std::string& att_text,
603 const std::vector<std::string>* extra_hit_sounds,
604 bool attacking)
605{
606 if(do_not_show_anims(disp) || (disp->fogged(a) && disp->fogged(b)) || !prefs::get().show_combat()) {
607 return;
608 }
609 //const unit_map& units = disp->get_units();
611
612 // scroll such that there is at least half a hex spacing around fighters
613 disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
614
615 log_scope("unit_attack");
616
617 const unit_map::const_iterator att = board.units().find(a);
618 assert(att.valid());
619 const unit& attacker = *att;
620
621 const unit_map::iterator def = board.find_unit(b);
622 assert(def.valid());
623 unit &defender = *def;
624 int def_hitpoints = defender.hitpoints();
625 const_attack_ptr weapon = attack.shared_from_this();
626
627 auto ctx = specials_context_t::make(
628 { attacker.shared_from_this(), a, weapon },
629 { defender.shared_from_this(), b, secondary_attack },
630 attacking);
631
632 att->set_facing(a.get_relative_dir(b));
633 def->set_facing(b.get_relative_dir(a));
634 defender.set_facing(b.get_relative_dir(a));
635
636 std::string text = number_and_text(damage, hit_text);
637 std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
638
639 strike_result::type hit_type;
640 if(damage >= defender.hitpoints()) {
641 hit_type = strike_result::type::kill;
642 } else if(damage > 0) {
643 hit_type = strike_result::type::hit;
644 }else {
645 hit_type = strike_result::type::miss;
646 }
647
648 unit_animator animator;
649
650 animator.add_animation(attacker.shared_from_this(), "attack", att->get_location(), def->get_location(), damage, true, text_2,
651 (drain_amount >= 0) ? color_t(0, 255, 0) : color_t(255, 0, 0), hit_type, weapon,
652 secondary_attack, swing);
653
654 // note that we take an anim from the real unit, we'll use it later
655 const unit_animation* defender_anim = def->anim_comp().choose_animation(def->get_location(), "defend",
656 att->get_location(), damage, hit_type, weapon, secondary_attack, swing);
657
658 animator.add_animation(defender.shared_from_this(), defender_anim, def->get_location(), true, text, {255, 0, 0});
659
660 active_ability_list leadership_list = ctx.get_abilities_weapons("leadership", attacker);
661 active_ability_list resistance_list = ctx.get_abilities_weapons("resistance", defender);
662 for(const active_ability& ability : leadership_list) {
663 if(ability.teacher_loc == a) {
664 continue;
665 }
666
667 if(ability.teacher_loc == b) {
668 continue;
669 }
670
671 unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
672 assert(leader.valid());
673 leader->set_facing(ability.teacher_loc.get_relative_dir(a));
674 leader->anim_comp().invalidate(*disp);
675 animator.add_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
676 att->get_location(), damage, true, "", {0,0,0},
677 hit_type, weapon, secondary_attack, swing);
678 }
679
680 for(const active_ability& ability : resistance_list) {
681 if(ability.teacher_loc == a) {
682 continue;
683 }
684
685 if(ability.teacher_loc == b) {
686 continue;
687 }
688
689 unit_map::const_iterator helper = board.units().find(ability.teacher_loc);
690 assert(helper.valid());
691 helper->set_facing(ability.teacher_loc.get_relative_dir(b));
692 animator.add_animation(helper.get_shared_ptr(), "resistance", ability.teacher_loc,
693 def->get_location(), damage, true, "", {0,0,0},
694 hit_type, weapon, secondary_attack, swing);
695 }
696
697 active_ability_list abilities = ctx.get_active_combat_teachers(*weapon);
698
699 for(const active_ability& ability : abilities) {
700 if(ability.teacher_loc == a) {
701 continue;
702 }
703
704 if(ability.teacher_loc == b) {
705 continue;
706 }
707
708 bool leading_playable = false;
709 bool helping_playable = false;
710 for(const active_ability& leader_list : leadership_list) {
711 if(ability.teacher_loc == leader_list.teacher_loc) {
712 leading_playable = true;
713 break;
714 }
715 }
716
717 for(const active_ability& helper_list : resistance_list) {
718 if(ability.teacher_loc == helper_list.teacher_loc) {
719 helping_playable = true;
720 break;
721 }
722 }
723
724 unit_map::const_iterator leader = board.units().find(ability.teacher_loc);
725 assert(leader.valid());
726 leader->set_facing(ability.teacher_loc.get_relative_dir(a));
727 if(animator.has_animation(leader.get_shared_ptr(), "leading", ability.teacher_loc,
728 att->get_location(), damage, hit_type, weapon, secondary_attack, swing) && leading_playable){
729 continue;
730 }
731 if(animator.has_animation(leader.get_shared_ptr(), "resistance", ability.teacher_loc,
732 def->get_location(), damage, hit_type, weapon, secondary_attack, swing) && helping_playable){
733 continue;
734 }
735 animator.add_animation(leader.get_shared_ptr(), "teaching", ability.teacher_loc,
736 att->get_location(), damage, true, "", {0,0,0},
737 hit_type, weapon, secondary_attack, swing);
738 }
739
740
741 animator.start_animations();
742 animator.wait_until(0ms);
743 int damage_left = damage;
744 bool extra_hit_sounds_played = false;
745 while(damage_left > 0 && !animator.would_end()) {
746 if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
747 for (std::string hit_sound : *extra_hit_sounds) {
748 sound::play_sound(hit_sound);
749 }
750 extra_hit_sounds_played = true;
751 }
752
753 auto step_left = (animator.get_end_time() - animator.get_elapsed_time() ) / 50ms;
754 if(step_left < 1) step_left = 1;
755 int removed_hp = damage_left/step_left ;
756 if(removed_hp < 1) removed_hp = 1;
757 defender.take_hit(removed_hp);
758 damage_left -= removed_hp;
759 animator.wait_until(animator.get_elapsed_time() + 50ms);
760 }
761 animator.wait_for_end();
762 // pass the animation back to the real unit
763 def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
764 reset_helpers(&*att, &*def);
765 def->set_hitpoints(def_hitpoints);
766}
767
768// private helper function, set all helpers to default position
769void reset_helpers(const unit *attacker,const unit *defender)
770{
772 const unit_map& units = disp->context().units();
773 if(attacker) {
774 active_ability_list attacker_abilities = attacker->get_abilities("leadership");
775 for(auto& special : abilities_list::all_weapon_tags()) {
776 attacker_abilities.append(attacker->get_abilities(special));
777 }
778 for(const active_ability& ability : attacker_abilities) {
779 unit_map::const_iterator leader = units.find(ability.teacher_loc);
780 assert(leader != units.end());
781 leader->anim_comp().set_standing();
782 }
783 }
784
785 if(defender) {
786 active_ability_list defender_abilities = defender->get_abilities("resistance");
787 for(auto& special : abilities_list::all_weapon_tags()) {
788 defender_abilities.append(defender->get_abilities(special));
789 }
790 for(const active_ability& ability : defender_abilities) {
791 unit_map::const_iterator helper = units.find(ability.teacher_loc);
792 assert(helper != units.end());
793 helper->anim_comp().set_standing();
794 }
795 }
796}
797
798void unit_recruited(const map_location& loc,const map_location& leader_loc)
799{
801 if(do_not_show_anims(disp) || (disp->fogged(loc) && disp->fogged(leader_loc))) {
802 return;
803 }
804
805 const team& viewing_team = disp->viewing_team();
806 const unit_map& units = disp->context().units();
807
809 if(u == units.end()) return;
810 const bool unit_visible = u->is_visible_to_team(viewing_team, false);
811
812 unit_map::const_iterator leader = units.find(leader_loc); // may be null_location
813 const bool leader_visible = (leader != units.end()) && leader->is_visible_to_team(viewing_team, false);
814
815 unit_animator animator;
816
817 {
818 ON_SCOPE_EXIT(u) {
819 u->set_hidden(false);
820 };
821 u->set_hidden(true);
822
823 if (leader_visible && unit_visible) {
824 disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
825 } else if (leader_visible) {
826 disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
827 } else if (unit_visible) {
828 disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
829 } else {
830 return;
831 }
832 if(leader != units.end()) {
833 leader->set_facing(leader_loc.get_relative_dir(loc));
834 if (leader_visible) {
835 animator.add_animation(leader.get_shared_ptr(), "recruiting", leader_loc, loc, 0, true);
836 }
837 }
838 }
839 u->anim_comp().reset_affect_adjacent(*disp);
840 animator.add_animation(u.get_shared_ptr(), "recruited", loc, leader_loc);
841 animator.start_animations();
842 animator.wait_for_end();
843 animator.set_all_standing();
844 if (loc==disp->mouseover_hex()) disp->invalidate_unit();
845}
846
847void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
848 const std::string & extra_text)
849{
851 const map_location& healed_loc = healed.get_location();
852 const bool some_healer_is_unfogged =
853 (healers.end() != std::find_if_not(healers.begin(), healers.end(),
854 [&](unit* h) { return disp->fogged(h->get_location()); }));
855
856 if(do_not_show_anims(disp) || (disp->fogged(healed_loc) && !some_healer_is_unfogged)) {
857 return;
858 }
859
860 // This is all the pretty stuff.
861 disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
862 disp->display_unit_hex(healed_loc);
863 unit_animator animator;
864
865 for (unit *h : healers) {
866 h->set_facing(h->get_location().get_relative_dir(healed_loc));
867 animator.add_animation(h->shared_from_this(), "healing", h->get_location(),
868 healed_loc, healing);
869 }
870
871 if (healing < 0) {
872 animator.add_animation(healed.shared_from_this(), "poisoned", healed_loc,
873 map_location::null_location(), -healing, false,
874 number_and_text(-healing, extra_text),
875 {255,0,0});
876 } else if ( healing > 0 ) {
877 animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
878 map_location::null_location(), healing, false,
879 number_and_text(healing, extra_text),
880 {0,255,0});
881 } else {
882 animator.add_animation(healed.shared_from_this(), "healed", healed_loc,
883 map_location::null_location(), 0, false,
884 extra_text, {0,255,0});
885 }
886 animator.start_animations();
887 animator.wait_for_end();
888 animator.set_all_standing();
889}
890
891} // end unit_display namespace
map_location loc
Definition move.cpp:172
void append(const active_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
virtual const unit_map & units() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition display.hpp:88
const team & viewing_team() const
Definition display.cpp:337
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition display.cpp:2927
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:1815
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition display.cpp:669
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition display.cpp:1723
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:1825
static display * get_singleton()
Returns the display object if a display object exists.
Definition display.hpp:102
const display_context & context() const
Definition display.hpp:187
virtual void select_hex(map_location hex)
Definition display.cpp:1381
const map_location & mouseover_hex() const
Definition display.hpp:302
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.
void reset()
Reset the internal unit pointer, and deregister from the manager.
Game board class.
team & get_team(int i)
unit_map::iterator find_unit(const map_location &loc)
virtual const unit_map & units() const override
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
static game_display * get_singleton()
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
bool is_skipping_replay() const
static prefs & get()
bool show_combat()
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
int side() const
Definition team.hpp:182
bool is_enemy(int n) const
Definition team.hpp:270
void reset_affect_adjacent(const unit_map &units)
Refresh map around unit if has ability with [affect_adjacent/distant] tag.
void set_standing(bool with_bars=true)
Sets the animation state to standing.
std::chrono::milliseconds get_elapsed_time() const
void pause_animation()
void resume_animation()
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,...
void wait_for_end() const
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)
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})
bool would_end() const
void start_animations()
void set_all_standing()
std::chrono::milliseconds get_end_time() const
void wait_until(const std::chrono::milliseconds &animation_time) const
A class to encapsulate the steps of drawing a unit's move.
Definition udisplay.hpp:45
unit_movement_animator(const unit_movement_animator &)=delete
std::chrono::milliseconds wait_until_
The animation potential to wait until.
Definition udisplay.hpp:69
void replace_temporary(const unit_ptr &u)
Makes the temporary unit used by this match the supplied unit.
Definition udisplay.cpp:216
void proceed_to(const unit_ptr &u, std::size_t path_index, bool update=false, bool wait=true)
Visually moves a unit from the last hex we drew to the one specified by path_index.
Definition udisplay.cpp:315
void update_shown_unit()
Switches the display back to *shown_unit_ after animating.
Definition udisplay.cpp:242
unit_ptr shown_unit_
The unit to be (re-)shown after an animation finishes.
Definition udisplay.hpp:71
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition udisplay.cpp:394
const std::vector< map_location > & path_
Definition udisplay.hpp:72
void finish(const unit_ptr &u, map_location::direction dir=map_location::direction::indeterminate)
Finishes the display of movement for the supplied unit.
Definition udisplay.cpp:434
void start(const unit_ptr &u)
Initiates the display of movement for the supplied unit.
Definition udisplay.cpp:257
Container associating units to locations.
Definition map.hpp:98
unit_iterator end()
Definition map.hpp:428
unit_iterator find(std::size_t id)
Definition map.cpp:302
This class represents a single unit of a specific type.
Definition unit.hpp:39
active_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
bool is_visible_to_team(const team &team, bool const see_all=true) const
Definition unit.cpp:2683
int hitpoints() const
The current number of hitpoints this unit has.
Definition unit.hpp:421
bool take_hit(int damage)
Damage the unit.
Definition unit.hpp:731
unit_animation_component & anim_comp() const
Definition unit.hpp:1537
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
void set_facing(map_location::direction dir) const
The this unit's facing.
Definition unit.cpp:1742
void set_location(const map_location &loc)
Sets this unit's map location.
Definition unit.hpp:1337
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition location.cpp:513
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition location.cpp:541
Standard logging facilities (interface).
#define log_scope(description)
Definition log.hpp:275
void pump()
Process all events currently in the queue.
Definition events.cpp:480
game_board * gameboard
Definition resources.cpp:20
fake_unit_manager * fake_units
Definition resources.cpp:30
play_controller * controller
Definition resources.cpp:21
void play_sound(const std::string &files, sound_tracks::type group, unsigned int repeats)
Definition sound.cpp:978
STL namespace.
Contains a number of free functions which display units.
Definition udisplay.cpp:39
void unit_die(const map_location &loc, unit &loser, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &winner_loc, const unit_ptr &winner)
Show a unit fading out.
Definition udisplay.cpp:571
void unit_draw_weapon(const map_location &loc, unit &attacker, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &defender_loc, const unit_ptr &defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
Definition udisplay.cpp:521
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const const_attack_ptr &secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds, bool attacking)
Make the unit on tile 'a' attack the unit on tile 'b'.
Definition udisplay.cpp:598
void unit_healing(unit &healed, const std::vector< unit * > &healers, int healing, const std::string &extra_text)
This will use a poisoning anim if healing<0.
Definition udisplay.cpp:847
void unit_recruited(const map_location &loc, const map_location &leader_loc)
Definition udisplay.cpp:798
void move_unit(const std::vector< map_location > &path, const unit_ptr &u, bool animate, map_location::direction dir, bool force_scroll)
Display a unit moving along a given path.
Definition udisplay.cpp:507
void unit_sheath_weapon(const map_location &primary_loc, const unit_ptr &primary_unit, const const_attack_ptr &primary_attack, const const_attack_ptr &secondary_attack, const map_location &secondary_loc, const unit_ptr &secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation.
Definition udisplay.cpp:541
void reset_helpers(const unit *attacker, const unit *defender)
Definition udisplay.cpp:769
bool headless()
The game is running headless.
Definition video.cpp:146
std::shared_ptr< const attack_type > const_attack_ptr
Definition ptr.hpp:34
std::shared_ptr< unit > unit_ptr
Definition ptr.hpp:26
#define ON_SCOPE_EXIT(...)
Run some arbitrary code (a lambda) when the current scope exits The lambda body follows this header,...
Data typedef for active_ability_list.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
Encapsulates the map of the game.
Definition location.hpp:46
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition location.cpp:238
static const map_location & null_location()
Definition location.hpp:103
bool valid() const
Definition map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition map.hpp:217
Display units performing various actions: moving, attacking, and dying.
#define h
#define b