1 /*
2 Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 /** @file */
16
17 #include "units/udisplay.hpp"
18
19 #include "fake_unit_manager.hpp"
20 #include "fake_unit_ptr.hpp"
21 #include "game_board.hpp"
22 #include "game_display.hpp"
23 #include "preferences/game.hpp"
24 #include "log.hpp"
25 #include "mouse_events.hpp"
26 #include "resources.hpp"
27 #include "color.hpp"
28 #include "sound.hpp"
29 #include "terrain/filter.hpp"
30 #include "units/unit.hpp"
31 #include "units/animation_component.hpp"
32 #include "units/filter.hpp"
33 #include "units/map.hpp"
34
35 #define LOG_DP LOG_STREAM(info, display)
36
37
38 /**
39 * Returns a string whose first line is @a number, centered over a second line,
40 * which consists of @a text.
41 * If the number is 0, the first line is suppressed.
42 */
number_and_text(int number,const std::string & text)43 static std::string number_and_text(int number, const std::string & text)
44 {
45 // Simple case.
46 if ( number == 0 )
47 return text;
48
49 std::ostringstream result;
50
51 if ( text.empty() )
52 result << number;
53 else
54 result << std::string((text.size()+1)/2, ' ') << number << '\n' << text;
55
56 return result.str();
57 }
58
59
60 /**
61 * Animates a teleportation between hexes.
62 *
63 * @param a The starting hex.
64 * @param b The ending hex.
65 * @param temp_unit The unit to animate (historically, a temporary unit).
66 * @param disp The game display. Assumed neither locked nor faked.
67 */
teleport_unit_between(const map_location & a,const map_location & b,unit & temp_unit,display & disp)68 static void teleport_unit_between(const map_location& a, const map_location& b,
69 unit& temp_unit, display& disp)
70 {
71 if ( disp.fogged(a) && disp.fogged(b) ) {
72 return;
73 }
74 const display_context& dc = disp.get_disp_context();
75 const team& viewing_team = dc.get_team(disp.viewing_side());
76
77 const bool a_visible = temp_unit.is_visible_to_team(a, viewing_team, dc, false);
78 const bool b_visible = temp_unit.is_visible_to_team(b, viewing_team, dc, false);
79
80 temp_unit.set_location(a);
81 if ( a_visible ) { // teleport
82 disp.invalidate(a);
83 temp_unit.set_facing(a.get_relative_dir(b));
84 if ( b_visible )
85 disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
86 else
87 disp.scroll_to_tile(a, game_display::ONSCREEN, true, false);
88 unit_animator animator;
89 animator.add_animation(&temp_unit,"pre_teleport",a);
90 animator.start_animations();
91 animator.wait_for_end();
92 }
93
94 temp_unit.set_location(b);
95 if ( b_visible ) { // teleport
96 disp.invalidate(b);
97 temp_unit.set_facing(a.get_relative_dir(b));
98 if ( a_visible )
99 disp.scroll_to_tiles(b, a, game_display::ONSCREEN, true, 0.0, false);
100 else
101 disp.scroll_to_tile(b, game_display::ONSCREEN, true, false);
102 unit_animator animator;
103 animator.add_animation(&temp_unit,"post_teleport",b);
104 animator.start_animations();
105 animator.wait_for_end();
106 }
107
108 temp_unit.anim_comp().set_standing();
109 disp.update_display();
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 * INT_MIN indicates that no animation is pending.
128 */
move_unit_between(const map_location & a,const map_location & b,unit_ptr temp_unit,unsigned int step_num,unsigned int step_left,unit_animator & animator,display & disp)129 static int move_unit_between(const map_location& a, const map_location& b,
130 unit_ptr temp_unit, unsigned int step_num,
131 unsigned int step_left, unit_animator & animator,
132 display& disp)
133 {
134 if ( disp.fogged(a) && disp.fogged(b) ) {
135 return INT_MIN;
136 }
137
138 temp_unit->set_location(a);
139 disp.invalidate(a);
140 temp_unit->set_facing(a.get_relative_dir(b));
141 animator.replace_anim_if_invalid(temp_unit.get(),"movement",a,b,step_num,
142 false,"",{0,0,0},unit_animation::hit_type::INVALID,nullptr,nullptr,step_left);
143 animator.start_animations();
144 animator.pause_animation();
145 disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
146 animator.restart_animation();
147
148 // useless now, previous short draw() just did one
149 // new_animation_frame();
150
151 int target_time = animator.get_animation_time_potential();
152 // target_time must be short to avoid jumpy move
153 // std::cout << "target time: " << target_time << "\n";
154 // we round it to the next multiple of 200 so that movement aligns to hex changes properly
155 target_time += 200;
156 target_time -= target_time%200;
157
158 return target_time;
159 }
160
161 namespace unit_display
162 {
163
164 /**
165 * The path must remain unchanged for the life of this object.
166 */
unit_mover(const std::vector<map_location> & path,bool animate,bool force_scroll)167 unit_mover::unit_mover(const std::vector<map_location>& path, bool animate, bool force_scroll) :
168 disp_(game_display::get_singleton()),
169 can_draw_(disp_ && !disp_->video().update_locked() &&
170 !disp_->video().faked() && path.size() > 1),
171 animate_(animate),
172 force_scroll_(force_scroll),
173 animator_(),
174 wait_until_(INT_MIN),
175 shown_unit_(),
176 path_(path),
177 current_(0),
178 temp_unit_ptr_(),
179 // Somewhat arbitrary default values.
180 was_hidden_(false),
181 is_enemy_(true)
182 {
183 // Some error conditions that indicate something has gone very wrong.
184 // (This class can handle these conditions, but someone wanted them
185 // to be assertions.)
186 assert(!path_.empty());
187 assert(disp_);
188 }
189
190
~unit_mover()191 unit_mover::~unit_mover()
192 {
193 // Make sure a unit hidden for movement is unhidden.
194 update_shown_unit();
195 // For safety, clear the animator before deleting the temp unit.
196 animator_.clear();
197 }
198
199
200 /**
201 * Makes the temporary unit used by this match the supplied unit.
202 * This is called when setting the initial unit, as well as replacing it with
203 * something new.
204 * When this finishes, the supplied unit is hidden, while the temporary unit
205 * is not hidden.
206 */
207 /* Note: Hide the unit in its current location; do not actually remove it.
208 * Otherwise the status displays will be wrong during the movement.
209 */
replace_temporary(unit_ptr u)210 void unit_mover::replace_temporary(unit_ptr u)
211 {
212 if ( disp_ == nullptr )
213 // No point in creating a temp unit with no way to display it.
214 return;
215
216 // Save the hidden state of the unit.
217 was_hidden_ = u->get_hidden();
218
219 // Make our temporary unit mostly match u...
220 temp_unit_ptr_ = fake_unit_ptr(u->clone(), resources::fake_units);
221
222 // ... but keep the temporary unhidden and hide the original.
223 temp_unit_ptr_->set_hidden(false);
224 u->set_hidden(true);
225
226 // Update cached data.
227 is_enemy_ = resources::gameboard->get_team(u->side()).is_enemy(disp_->viewing_side());
228 }
229
230
231 /**
232 * Switches the display back to *shown_unit_ after animating.
233 * This uses temp_unit_ptr_, so (in the destructor) call this before deleting
234 * temp_unit_ptr_.
235 */
update_shown_unit()236 void unit_mover::update_shown_unit()
237 {
238 if ( shown_unit_ ) {
239 // Switch the display back to the real unit.
240 shown_unit_->set_hidden(was_hidden_);
241 temp_unit_ptr_->set_hidden(true);
242 shown_unit_.reset();
243 }
244 }
245
246
247 /**
248 * Initiates the display of movement for the supplied unit.
249 * This should be called before attempting to display moving to a new hex.
250 */
start(unit_ptr u)251 void unit_mover::start(unit_ptr u)
252 {
253 // Nothing to do here if there is nothing to animate.
254 if ( !can_draw_ )
255 return;
256 // If no animation then hide unit until end of movement
257 if ( !animate_ ) {
258 was_hidden_ = u->get_hidden();
259 u->set_hidden(true);
260 return;
261 }
262
263 // This normally does nothing, but just in case...
264 wait_for_anims();
265
266 // Visually replace the original unit with the temporary.
267 // (Original unit is left on the map, so the unit count is correct.)
268 replace_temporary(u);
269
270 // Initialize our temporary unit for the move.
271 temp_unit_ptr_->set_location(path_[0]);
272 temp_unit_ptr_->set_facing(path_[0].get_relative_dir(path_[1]));
273 temp_unit_ptr_->anim_comp().set_standing(false);
274 disp_->invalidate(path_[0]);
275
276 // If the unit can be seen here by the viewing side:
277 if ( !is_enemy_ || !temp_unit_ptr_->invisible(path_[0], disp_->get_disp_context()) ) {
278 // Scroll to the path, but only if it fully fits on screen.
279 // If it does not fit we might be able to do a better scroll later.
280 disp_->scroll_to_tiles(path_, game_display::ONSCREEN, true, true, 0.0, false);
281 }
282
283 // extra immobile movement animation for take-off
284 animator_.add_animation(temp_unit_ptr_.get(), "pre_movement", path_[0], path_[1]);
285 animator_.start_animations();
286 animator_.wait_for_end();
287 animator_.clear();
288
289 // Switch the display back to the real unit.
290 u->set_facing(temp_unit_ptr_->facing());
291 u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
292 u->set_hidden(was_hidden_);
293 temp_unit_ptr_->set_hidden(true);
294 }
295
296
297 /**
298 * Visually moves a unit from the last hex we drew to the one specified by
299 * @a path_index. If @a path_index points to an earlier hex, we do nothing.
300 * The moving unit will only be updated if update is set to true; otherwise,
301 * the provided unit is merely hidden during the movement and re-shown after.
302 * (Not updating the unit can produce smoother animations in some cases.)
303 * If @a wait is set to false, this returns without waiting for the final
304 * animation to finish. Call wait_for_anims() to explicitly get this final
305 * wait (another call to proceed_to() or finish() will implicitly wait). The
306 * unit must remain valid until the wait is finished.
307 */
proceed_to(unit_ptr u,size_t path_index,bool update,bool wait)308 void unit_mover::proceed_to(unit_ptr u, size_t path_index, bool update, bool wait)
309 {
310 // Nothing to do here if animations cannot be shown.
311 if ( !can_draw_ || !animate_ )
312 return;
313
314 // Handle pending visibility issues before introducing new ones.
315 wait_for_anims();
316
317 if ( update || !temp_unit_ptr_ )
318 // Replace the temp unit (which also hides u and shows our temporary).
319 replace_temporary(u);
320 else
321 {
322 // Just switch the display from the real unit to our fake one.
323 temp_unit_ptr_->set_hidden(false);
324 u->set_hidden(true);
325 }
326
327 // Safety check.
328 path_index = std::min(path_index, path_.size()-1);
329
330 for ( ; current_ < path_index; ++current_ ) {
331 // It is possible for path_[current_] and path_[current_+1] not to be adjacent.
332 // When that is the case, and the unit is invisible at path_[current_], we shouldn't
333 // scroll to that hex.
334 std::vector<map_location> locs;
335 if (!temp_unit_ptr_->invisible(path_[current_], disp_->get_disp_context()))
336 locs.push_back(path_[current_]);
337 if (!temp_unit_ptr_->invisible(path_[current_+1], disp_->get_disp_context()))
338 locs.push_back(path_[current_+1]);
339 // If the unit can be seen by the viewing side while making this step:
340 if ( !is_enemy_ || !locs.empty() )
341 {
342 // Wait for the previous step to complete before drawing the next one.
343 wait_for_anims();
344
345 if ( !disp_->tile_fully_on_screen(path_[current_]) ||
346 !disp_->tile_fully_on_screen(path_[current_+1]))
347 {
348 // prevent the unit from disappearing if we scroll here with i == 0
349 temp_unit_ptr_->set_location(path_[current_]);
350 disp_->invalidate(path_[current_]);
351 // scroll in as much of the remaining path as possible
352 if ( temp_unit_ptr_->anim_comp().get_animation() )
353 temp_unit_ptr_->anim_comp().get_animation()->pause_animation();
354 disp_->scroll_to_tiles(locs, game_display::ONSCREEN,
355 true, false, 0.0, force_scroll_);
356 if ( temp_unit_ptr_->anim_comp().get_animation() )
357 temp_unit_ptr_->anim_comp().get_animation()->restart_animation();
358 }
359
360 if ( tiles_adjacent(path_[current_], path_[current_+1]) )
361 wait_until_ =
362 move_unit_between(path_[current_], path_[current_+1],
363 temp_unit_ptr_.get_unit_ptr(), current_,
364 path_.size() - (current_+2), animator_,
365 *disp_);
366 else if ( path_[current_] != path_[current_+1] )
367 teleport_unit_between(path_[current_], path_[current_+1],
368 *temp_unit_ptr_, *disp_);
369 }
370 }
371
372 // Update the unit's facing.
373 u->set_facing(temp_unit_ptr_->facing());
374 u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
375 // Remember the unit to unhide when the animation finishes.
376 shown_unit_ = u;
377 if ( wait )
378 wait_for_anims();
379 }
380
381
382 /**
383 * Waits for the final animation of the most recent proceed_to() to finish.
384 * It is not necessary to call this unless you want to wait before the next
385 * call to proceed_to() or finish().
386 */
wait_for_anims()387 void unit_mover::wait_for_anims()
388 {
389 if ( wait_until_ == INT_MAX )
390 // Wait for end (not currently used, but still supported).
391 animator_.wait_for_end();
392 else if ( wait_until_ != INT_MIN ) {
393 // Wait until the specified time (used for normal movement).
394 animator_.wait_until(wait_until_);
395 // debug code, see unit_frame::redraw()
396 // std::cout << " end\n";
397 /// @todo For wesnoth 1.14+: check if efficient for redrawing?
398 /// Check with large animated units too make sure artifacts are
399 /// not left on screen after unit movement in particular.
400 if ( disp_ ) { // Should always be true if we get here.
401 // Invalidate the hexes around the move that prompted this wait.
402 adjacent_loc_array_t arr;
403 get_adjacent_tiles(path_[current_-1], arr.data());
404 for ( unsigned i = 0; i < arr.size(); ++i )
405 disp_->invalidate(arr[i]);
406 get_adjacent_tiles(path_[current_], arr.data());
407 for ( unsigned i = 0; i < arr.size(); ++i )
408 disp_->invalidate(arr[i]);
409 }
410 }
411
412 // Reset data.
413 wait_until_ = INT_MIN;
414 animator_.clear();
415
416 update_shown_unit();
417 }
418
419
420 /**
421 * Finishes the display of movement for the supplied unit.
422 * If called before showing the unit reach the end of the path, it will be
423 * assumed that the movement ended early.
424 * If @a dir is not supplied, the final direction will be determined by (the
425 * last two traversed hexes of) the path.
426 */
finish(unit_ptr u,map_location::DIRECTION dir)427 void unit_mover::finish(unit_ptr u, map_location::DIRECTION dir)
428 {
429 // Nothing to do here if the display is not valid.
430 if ( !can_draw_ ) {
431 // Make sure to reset the unit's animation to deal with a quirk in the
432 // action engine where it leaves it to us to reenable bars even if the
433 // display is initially locked.
434 u->anim_comp().set_standing(true);
435 return;
436 }
437
438 const map_location & end_loc = path_[current_];
439 const map_location::DIRECTION final_dir = current_ == 0 ?
440 path_[0].get_relative_dir(path_[1]) :
441 path_[current_-1].get_relative_dir(end_loc);
442
443 if ( animate_ )
444 {
445 wait_for_anims(); // In case proceed_to() did not wait for the last animation.
446
447 // Make sure the displayed unit is correct.
448 replace_temporary(u);
449 temp_unit_ptr_->set_location(end_loc);
450 temp_unit_ptr_->set_facing(final_dir);
451
452 // Animation
453 animator_.add_animation(temp_unit_ptr_.get(), "post_movement", end_loc);
454 animator_.start_animations();
455 animator_.wait_for_end();
456 animator_.clear();
457
458 // Switch the display back to the real unit.
459 u->set_hidden(was_hidden_);
460 temp_unit_ptr_->set_hidden(true);
461
462 events::mouse_handler* mousehandler = events::mouse_handler::get_singleton();
463 if ( mousehandler ) {
464 mousehandler->invalidate_reachmap();
465 }
466 }
467 else
468 {
469 // Show the unit at end of skipped animation
470 u->set_hidden(was_hidden_);
471 }
472
473 // Facing gets set even when not animating.
474 u->set_facing(dir == map_location::NDIRECTIONS ? final_dir : dir);
475 u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
476
477 // Redraw path ends (even if not animating).
478 disp_->invalidate(path_.front());
479 disp_->invalidate(end_loc);
480 }
481
482
483 /**
484 * Display a unit moving along a given path.
485 *
486 * @param path The path to traverse.
487 * @param u The unit to show being moved. Its facing will be updated,
488 * but not its position.
489 * @param animate If set to false, only side-effects of move are applied
490 * (correct unit facing, path hexes redrawing).
491 * @param dir Unit will be set facing this direction after move.
492 * If nothing passed, direction will be set based on path.
493 */
494 /* Note: Hide the unit in its current location,
495 * but don't actually remove it until the move is done,
496 * so that while the unit is moving status etc.
497 * will still display the correct number of units.
498 */
move_unit(const std::vector<map_location> & path,unit_ptr u,bool animate,map_location::DIRECTION dir,bool force_scroll)499 void move_unit(const std::vector<map_location>& path, unit_ptr u,
500 bool animate, map_location::DIRECTION dir,
501 bool force_scroll)
502 {
503 unit_mover mover(path, animate, force_scroll);
504
505 mover.start(u);
506 mover.proceed_to(u, path.size());
507 mover.finish(u, dir);
508 }
509
510
511 void reset_helpers(const unit *attacker,const unit *defender);
512
unit_draw_weapon(const map_location & loc,unit & attacker,const_attack_ptr attack,const_attack_ptr secondary_attack,const map_location & defender_loc,unit * defender)513 void unit_draw_weapon(const map_location& loc, unit& attacker,
514 const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& defender_loc,unit* defender)
515 {
516 display* disp = display::get_singleton();
517 if(!disp ||disp->video().update_locked() || disp->video().faked() || disp->fogged(loc) || preferences::show_combat() == false) {
518 return;
519 }
520 unit_animator animator;
521 attacker.set_facing(loc.get_relative_dir(defender_loc));
522 defender->set_facing(defender_loc.get_relative_dir(loc));
523 animator.add_animation(&attacker,"draw_weapon",loc,defender_loc,0,true,"",{0,0,0},unit_animation::hit_type::HIT,attack,secondary_attack,0);
524 animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,true,"",{0,0,0},unit_animation::hit_type::MISS,secondary_attack,attack,0);
525 animator.start_animations();
526 animator.wait_for_end();
527
528 }
529
530
unit_sheath_weapon(const map_location & primary_loc,unit * primary_unit,const_attack_ptr primary_attack,const_attack_ptr secondary_attack,const map_location & secondary_loc,unit * secondary_unit)531 void unit_sheath_weapon(const map_location& primary_loc, unit* primary_unit,
532 const_attack_ptr primary_attack,const_attack_ptr secondary_attack, const map_location& secondary_loc,unit* secondary_unit)
533 {
534 display* disp = display::get_singleton();
535 if(!disp ||disp->video().update_locked() || disp->video().faked() || disp->fogged(primary_loc) || preferences::show_combat() == false) {
536 return;
537 }
538 unit_animator animator;
539 if(primary_unit) {
540 animator.add_animation(primary_unit,"sheath_weapon",primary_loc,secondary_loc,0,true,"",{0,0,0},unit_animation::hit_type::INVALID,primary_attack,secondary_attack,0);
541 }
542 if(secondary_unit) {
543 animator.add_animation(secondary_unit,"sheath_weapon",secondary_loc,primary_loc,0,true,"",{0,0,0},unit_animation::hit_type::INVALID,secondary_attack,primary_attack,0);
544 }
545
546 if(primary_unit || secondary_unit) {
547 animator.start_animations();
548 animator.wait_for_end();
549 }
550 if(primary_unit) {
551 primary_unit->anim_comp().set_standing();
552 }
553 if(secondary_unit) {
554 secondary_unit->anim_comp().set_standing();
555 }
556 reset_helpers(primary_unit,secondary_unit);
557
558 }
559
560
unit_die(const map_location & loc,unit & loser,const_attack_ptr attack,const_attack_ptr secondary_attack,const map_location & winner_loc,unit * winner)561 void unit_die(const map_location& loc, unit& loser,
562 const_attack_ptr attack,const_attack_ptr secondary_attack, const map_location& winner_loc,unit* winner)
563 {
564 display* disp = display::get_singleton();
565 if(!disp ||disp->video().update_locked() || disp->video().faked() || disp->fogged(loc) || preferences::show_combat() == false) {
566 return;
567 }
568 unit_animator animator;
569 // hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
570 animator.add_animation(&loser,"death",loc,winner_loc,0,false,"",{0,0,0},unit_animation::hit_type::KILL,attack,secondary_attack,0);
571 // but show the bars of the winner (avoid blinking and show its xp gain)
572 animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",{0,0,0},
573 unit_animation::hit_type::KILL,secondary_attack,attack,0);
574 animator.start_animations();
575 animator.wait_for_end();
576
577 reset_helpers(winner,&loser);
578 events::mouse_handler* mousehandler = events::mouse_handler::get_singleton();
579 if (mousehandler) {
580 mousehandler->invalidate_reachmap();
581 }
582 }
583
584
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)585 void unit_attack(display * disp, game_board & board,
586 const map_location& a, const map_location& b, int damage,
587 const attack_type& attack, const_attack_ptr secondary_attack,
588 int swing,const std::string& hit_text,int drain_amount,const std::string& att_text, const std::vector<std::string>* extra_hit_sounds)
589 {
590 if(!disp ||disp->video().update_locked() || disp->video().faked() ||
591 (disp->fogged(a) && disp->fogged(b)) || preferences::show_combat() == false) {
592 return;
593 }
594 //const unit_map& units = disp->get_units();
595 disp->select_hex(map_location::null_location());
596
597 // scroll such that there is at least half a hex spacing around fighters
598 disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
599
600 log_scope("unit_attack");
601
602 const unit_map::const_iterator att = board.units().find(a);
603 assert(att.valid());
604 const unit& attacker = *att;
605
606 const unit_map::iterator def = board.find_unit(b);
607 assert(def.valid());
608 unit &defender = *def;
609 int def_hitpoints = defender.hitpoints();
610
611 att->set_facing(a.get_relative_dir(b));
612 def->set_facing(b.get_relative_dir(a));
613 defender.set_facing(b.get_relative_dir(a));
614
615
616 unit_animator animator;
617 unit_ability_list leaders = attacker.get_abilities("leadership");
618 unit_ability_list helpers = defender.get_abilities("resistance");
619
620 std::string text = number_and_text(damage, hit_text);
621 std::string text_2 = number_and_text(std::abs(drain_amount), att_text);
622
623 unit_animation::hit_type hit_type;
624 if(damage >= defender.hitpoints()) {
625 hit_type = unit_animation::hit_type::KILL;
626 } else if(damage > 0) {
627 hit_type = unit_animation::hit_type::HIT;
628 }else {
629 hit_type = unit_animation::hit_type::MISS;
630 }
631 animator.add_animation(&attacker, "attack", att->get_location(),
632 def->get_location(), damage, true, text_2,
633 (drain_amount >= 0) ? color_t(0,255,0) : color_t(255,0,0),
634 hit_type, attack.shared_from_this(), secondary_attack, swing);
635
636 // note that we take an anim from the real unit, we'll use it later
637 const unit_animation *defender_anim = def->anim_comp().choose_animation(*disp,
638 def->get_location(), "defend", att->get_location(), damage,
639 hit_type, attack.shared_from_this(), secondary_attack, swing);
640 animator.add_animation(&defender, defender_anim, def->get_location(),
641 true, text , {255,0,0});
642
643 for (const unit_ability & ability : leaders) {
644 if(ability.second == a) continue;
645 if(ability.second == b) continue;
646 unit_map::const_iterator leader = board.units().find(ability.second);
647 assert(leader.valid());
648 leader->set_facing(ability.second.get_relative_dir(a));
649 animator.add_animation(&*leader, "leading", ability.second,
650 att->get_location(), damage, true, "", {0,0,0},
651 hit_type, attack.shared_from_this(), secondary_attack, swing);
652 }
653 for (const unit_ability & ability : helpers) {
654 if(ability.second == a) continue;
655 if(ability.second == b) continue;
656 unit_map::const_iterator helper = board.units().find(ability.second);
657 assert(helper.valid());
658 helper->set_facing(ability.second.get_relative_dir(b));
659 animator.add_animation(&*helper, "resistance", ability.second,
660 def->get_location(), damage, true, "", {0,0,0},
661 hit_type, attack.shared_from_this(), secondary_attack, swing);
662 }
663
664
665 animator.start_animations();
666 animator.wait_until(0);
667 int damage_left = damage;
668 bool extra_hit_sounds_played = false;
669 while(damage_left > 0 && !animator.would_end()) {
670 if(!extra_hit_sounds_played && extra_hit_sounds != nullptr) {
671 for (std::string hit_sound : *extra_hit_sounds) {
672 sound::play_sound(hit_sound);
673 }
674 extra_hit_sounds_played = true;
675 }
676
677 int step_left = (animator.get_end_time() - animator.get_animation_time() )/50;
678 if(step_left < 1) step_left = 1;
679 int removed_hp = damage_left/step_left ;
680 if(removed_hp < 1) removed_hp = 1;
681 defender.take_hit(removed_hp);
682 damage_left -= removed_hp;
683 animator.wait_until(animator.get_animation_time_potential() +50);
684 }
685 animator.wait_for_end();
686 // pass the animation back to the real unit
687 def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
688 reset_helpers(&*att, &*def);
689 def->set_hitpoints(def_hitpoints);
690 }
691
692 // private helper function, set all helpers to default position
reset_helpers(const unit * attacker,const unit * defender)693 void reset_helpers(const unit *attacker,const unit *defender)
694 {
695 display* disp = display::get_singleton();
696 const unit_map& units = disp->get_units();
697 if(attacker) {
698 unit_ability_list leaders = attacker->get_abilities("leadership");
699 for (const unit_ability & ability : leaders) {
700 unit_map::const_iterator leader = units.find(ability.second);
701 assert(leader != units.end());
702 leader->anim_comp().set_standing();
703 }
704 }
705
706 if(defender) {
707 unit_ability_list helpers = defender->get_abilities("resistance");
708 for (const unit_ability & ability : helpers) {
709 unit_map::const_iterator helper = units.find(ability.second);
710 assert(helper != units.end());
711 helper->anim_comp().set_standing();
712 }
713 }
714 }
715
unit_recruited(const map_location & loc,const map_location & leader_loc)716 void unit_recruited(const map_location& loc,const map_location& leader_loc)
717 {
718 game_display* disp = game_display::get_singleton();
719 const display_context& dc = disp->get_disp_context();
720 const team& viewing_team = dc.get_team(disp->viewing_side());
721 if(!disp || disp->video().update_locked() || disp->video().faked() ||
722 (disp->fogged(loc) && disp->fogged(leader_loc))) return;
723
724 unit_map::const_iterator u = disp->get_units().find(loc);
725 if(u == disp->get_units().end()) return;
726 const bool unit_visible = u->is_visible_to_team(viewing_team, dc, false);
727
728 unit_map::const_iterator leader = disp->get_units().find(leader_loc); // may be null_location
729 const bool leader_visible = (leader != disp->get_units().end()) && leader->is_visible_to_team(viewing_team, dc, false);
730
731 u->set_hidden(true);
732
733 unit_animator animator;
734 if (leader_visible && unit_visible) {
735 disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
736 } else if (leader_visible) {
737 disp->scroll_to_tile(leader_loc,game_display::ONSCREEN,true,false);
738 } else if (unit_visible) {
739 disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
740 } else {
741 return;
742 }
743 if (leader != disp->get_units().end()) {
744 leader->set_facing(leader_loc.get_relative_dir(loc));
745 if (leader_visible) {
746 animator.add_animation(&*leader, "recruiting", leader_loc, loc, 0, true);
747 }
748 }
749
750 disp->draw();
751 u->set_hidden(false);
752 animator.add_animation(&*u, "recruited", loc, leader_loc);
753 animator.start_animations();
754 animator.wait_for_end();
755 animator.set_all_standing();
756 if (loc==disp->mouseover_hex()) disp->invalidate_unit();
757 }
758
unit_healing(unit & healed,const std::vector<unit * > & healers,int healing,const std::string & extra_text)759 void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
760 const std::string & extra_text)
761 {
762 game_display* disp = game_display::get_singleton();
763 const map_location &healed_loc = healed.get_location();
764 if(!disp || disp->video().update_locked() || disp->video().faked() || disp->fogged(healed_loc)) return;
765
766 // This is all the pretty stuff.
767 disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
768 disp->display_unit_hex(healed_loc);
769 unit_animator animator;
770
771 for (unit *h : healers) {
772 h->set_facing(h->get_location().get_relative_dir(healed_loc));
773 animator.add_animation(h, "healing", h->get_location(),
774 healed_loc, healing);
775 }
776
777 if (healing < 0) {
778 animator.add_animation(&healed, "poisoned", healed_loc,
779 map_location::null_location(), -healing, false,
780 number_and_text(-healing, extra_text),
781 {255,0,0});
782 } else if ( healing > 0 ) {
783 animator.add_animation(&healed, "healed", healed_loc,
784 map_location::null_location(), healing, false,
785 number_and_text(healing, extra_text),
786 {0,255,0});
787 } else {
788 animator.add_animation(&healed, "healed", healed_loc,
789 map_location::null_location(), 0, false,
790 extra_text, {0,255,0});
791 }
792 animator.start_animations();
793 animator.wait_for_end();
794 animator.set_all_standing();
795 }
796
797 } // end unit_display namespace
798