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