mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-06 15:23:33 +00:00
875 lines
30 KiB
C++
875 lines
30 KiB
C++
/*
|
|
Copyright (C) 2003 - 2014 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/** @file */
|
|
|
|
#include "global.hpp"
|
|
#include "unit_display.hpp"
|
|
|
|
#include "fake_unit_manager.hpp"
|
|
#include "fake_unit_ptr.hpp"
|
|
#include "game_board.hpp"
|
|
#include "game_display.hpp"
|
|
#include "game_preferences.hpp"
|
|
#include "log.hpp"
|
|
#include "mouse_events.hpp"
|
|
#include "resources.hpp"
|
|
#include "terrain_filter.hpp"
|
|
#include "unit.hpp"
|
|
#include "unit_animation_component.hpp"
|
|
#include "unit_filter.hpp"
|
|
#include "unit_map.hpp"
|
|
|
|
#include <boost/foreach.hpp>
|
|
|
|
#define LOG_DP LOG_STREAM(info, display)
|
|
|
|
|
|
/**
|
|
* Returns a string whose first line is @a number, centered over a second line,
|
|
* which consists of @a text.
|
|
* If the number is 0, the first line is suppressed.
|
|
*/
|
|
static std::string number_and_text(int number, const std::string & text)
|
|
{
|
|
// Simple case.
|
|
if ( number == 0 )
|
|
return text;
|
|
|
|
std::ostringstream result;
|
|
|
|
if ( text.empty() )
|
|
result << number;
|
|
else
|
|
result << std::string((text.size()+1)/2, ' ') << number << '\n' << text;
|
|
|
|
return result.str();
|
|
}
|
|
|
|
|
|
/**
|
|
* Animates a teleportation between hexes.
|
|
*
|
|
* @param a The starting hex.
|
|
* @param b The ending hex.
|
|
* @param temp_unit The unit to animate (historically, a temporary unit).
|
|
* @param disp The game display. Assumed neither locked nor faked.
|
|
*/
|
|
static void teleport_unit_between(const map_location& a, const map_location& b,
|
|
unit& temp_unit, display& disp)
|
|
{
|
|
if ( disp.fogged(a) && disp.fogged(b) ) {
|
|
return;
|
|
}
|
|
|
|
temp_unit.set_location(a);
|
|
if ( !disp.fogged(a) ) { // teleport
|
|
disp.invalidate(a);
|
|
temp_unit.set_facing(a.get_relative_dir(b));
|
|
disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
|
|
unit_animator animator;
|
|
animator.add_animation(&temp_unit,"pre_teleport",a);
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
}
|
|
|
|
temp_unit.set_location(b);
|
|
if ( !disp.fogged(b) ) { // teleport
|
|
disp.invalidate(b);
|
|
temp_unit.set_facing(a.get_relative_dir(b));
|
|
disp.scroll_to_tiles(b, a, game_display::ONSCREEN, true, 0.0, false);
|
|
unit_animator animator;
|
|
animator.add_animation(&temp_unit,"post_teleport",b);
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
}
|
|
|
|
temp_unit.anim_comp().set_standing();
|
|
disp.update_display();
|
|
events::pump();
|
|
}
|
|
|
|
/**
|
|
* Animates a single step between hexes.
|
|
* This will return before the animation actually finishes, allowing other
|
|
* processing to occur during the animation.
|
|
*
|
|
* @param a The starting hex.
|
|
* @param b The ending hex.
|
|
* @param temp_unit The unit to animate (historically, a temporary unit).
|
|
* @param step_num The number of steps taken so far (used to pick an animation).
|
|
* @param step_left The number of steps remaining (used to pick an animation).
|
|
* @param animator The unit_animator to use. This is assumed clear when we start,
|
|
* but will likely not be clear when we return.
|
|
* @param disp The game display. Assumed neither locked nor faked.
|
|
* @returns The animation potential until this animation will finish.
|
|
* INT_MIN indicates that no animation is pending.
|
|
*/
|
|
static int 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)
|
|
{
|
|
if ( disp.fogged(a) && disp.fogged(b) ) {
|
|
return INT_MIN;
|
|
}
|
|
|
|
temp_unit->set_location(a);
|
|
disp.invalidate(a);
|
|
temp_unit->set_facing(a.get_relative_dir(b));
|
|
animator.replace_anim_if_invalid(temp_unit.get(),"movement",a,b,step_num,
|
|
false,"",0,unit_animation::INVALID,NULL,NULL,step_left);
|
|
animator.start_animations();
|
|
animator.pause_animation();
|
|
disp.scroll_to_tiles(a, b, game_display::ONSCREEN, true, 0.0, false);
|
|
animator.restart_animation();
|
|
|
|
// useless now, previous short draw() just did one
|
|
// new_animation_frame();
|
|
|
|
int target_time = animator.get_animation_time_potential();
|
|
// target_time must be short to avoid jumpy move
|
|
// std::cout << "target time: " << target_time << "\n";
|
|
// we round it to the next multiple of 200
|
|
target_time += 200;
|
|
target_time -= target_time%200;
|
|
|
|
// This code causes backwards teleport because the time > 200 causes offset > 1.0
|
|
// which will not match with the following -1.0
|
|
// if( target_time - animator.get_animation_time_potential() < 100 ) target_time +=200;
|
|
|
|
return target_time;
|
|
}
|
|
|
|
namespace unit_display
|
|
{
|
|
|
|
/**
|
|
* The path must remain unchanged for the life of this object.
|
|
*/
|
|
unit_mover::unit_mover(const std::vector<map_location>& path, bool animate, bool force_scroll) :
|
|
disp_(game_display::get_singleton()),
|
|
can_draw_(disp_ && !disp_->video().update_locked() &&
|
|
!disp_->video().faked() && path.size() > 1),
|
|
animate_(animate),
|
|
force_scroll_(force_scroll),
|
|
animator_(),
|
|
wait_until_(INT_MIN),
|
|
shown_unit_(),
|
|
path_(path),
|
|
current_(0),
|
|
temp_unit_ptr_(),
|
|
// Somewhat arbitrary default values.
|
|
was_hidden_(false),
|
|
is_enemy_(true)
|
|
{
|
|
// Some error conditions that indicate something has gone very wrong.
|
|
// (This class can handle these conditions, but someone wanted them
|
|
// to be assertions.)
|
|
assert(!path_.empty());
|
|
assert(disp_);
|
|
}
|
|
|
|
|
|
unit_mover::~unit_mover()
|
|
{
|
|
// Make sure a unit hidden for movement is unhidden.
|
|
update_shown_unit();
|
|
// For safety, clear the animator before deleting the temp unit.
|
|
animator_.clear();
|
|
}
|
|
|
|
|
|
/**
|
|
* Makes the temporary unit used by this match the supplied unit.
|
|
* This is called when setting the initial unit, as well as replacing it with
|
|
* something new.
|
|
* When this finishes, the supplied unit is hidden, while the temporary unit
|
|
* is not hidden.
|
|
*/
|
|
/* Note: Hide the unit in its current location; do not actually remove it.
|
|
* Otherwise the status displays will be wrong during the movement.
|
|
*/
|
|
void unit_mover::replace_temporary(unit_ptr u)
|
|
{
|
|
if ( disp_ == NULL )
|
|
// No point in creating a temp unit with no way to display it.
|
|
return;
|
|
|
|
// Save the hidden state of the unit.
|
|
was_hidden_ = u->get_hidden();
|
|
|
|
// Make our temporary unit mostly match u...
|
|
temp_unit_ptr_ = fake_unit_ptr(unit_ptr(new unit(*u)), resources::fake_units);
|
|
|
|
// ... but keep the temporary unhidden and hide the original.
|
|
temp_unit_ptr_->set_hidden(false);
|
|
u->set_hidden(true);
|
|
|
|
// Update cached data.
|
|
is_enemy_ = (*resources::teams)[u->side()-1].is_enemy(disp_->viewing_side());
|
|
}
|
|
|
|
|
|
/**
|
|
* Switches the display back to *shown_unit_ after animating.
|
|
* This uses temp_unit_ptr_, so (in the destructor) call this before deleting
|
|
* temp_unit_ptr_.
|
|
*/
|
|
void unit_mover::update_shown_unit()
|
|
{
|
|
if ( shown_unit_ ) {
|
|
// Switch the display back to the real unit.
|
|
shown_unit_->set_hidden(was_hidden_);
|
|
temp_unit_ptr_->set_hidden(true);
|
|
shown_unit_.reset();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Initiates the display of movement for the supplied unit.
|
|
* This should be called before attempting to display moving to a new hex.
|
|
*/
|
|
void unit_mover::start(unit_ptr u)
|
|
{
|
|
// Nothing to do here if there is nothing to animate.
|
|
if ( !can_draw_ )
|
|
return;
|
|
// If no animation then hide unit until end of movement
|
|
if ( !animate_ ) {
|
|
was_hidden_ = u->get_hidden();
|
|
u->set_hidden(true);
|
|
return;
|
|
}
|
|
|
|
// This normally does nothing, but just in case...
|
|
wait_for_anims();
|
|
|
|
// Visually replace the original unit with the temporary.
|
|
// (Original unit is left on the map, so the unit count is correct.)
|
|
replace_temporary(u);
|
|
|
|
// Initialize our temporary unit for the move.
|
|
temp_unit_ptr_->set_location(path_[0]);
|
|
temp_unit_ptr_->set_facing(path_[0].get_relative_dir(path_[1]));
|
|
temp_unit_ptr_->anim_comp().set_standing(false);
|
|
disp_->invalidate(path_[0]);
|
|
|
|
// If the unit can be seen here by the viewing side:
|
|
if ( !is_enemy_ || !temp_unit_ptr_->invisible(path_[0]) ) {
|
|
// Scroll to the path, but only if it fully fits on screen.
|
|
// If it does not fit we might be able to do a better scroll later.
|
|
disp_->scroll_to_tiles(path_, game_display::ONSCREEN, true, true, 0.0, false);
|
|
}
|
|
// We need to clear big invalidation before the move and have a smooth animation
|
|
// (mainly black stripes and invalidation after canceling attack dialog).
|
|
// Two draw calls are needed to also redraw the previously invalidated hexes.
|
|
// We use update=false because we don't need delay here (no time wasted)
|
|
// and no screen refresh (will be done by last 3rd draw() and it optimizes
|
|
// the double blitting done by these invalidations).
|
|
disp_->draw(false);
|
|
disp_->draw(false);
|
|
|
|
// The last draw() was still slow, and its initial new_animation_frame() call
|
|
// is now old, so we do another draw() to get a fresh one
|
|
// TODO: replace that by a new_animation_frame() before starting anims
|
|
// don't forget to change the previous draw(false) to true
|
|
disp_->draw(true);
|
|
|
|
// extra immobile movement animation for take-off
|
|
animator_.add_animation(temp_unit_ptr_.get(), "pre_movement", path_[0], path_[1]);
|
|
animator_.start_animations();
|
|
animator_.wait_for_end();
|
|
animator_.clear();
|
|
|
|
// Switch the display back to the real unit.
|
|
u->set_facing(temp_unit_ptr_->facing());
|
|
u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
|
|
u->set_hidden(was_hidden_);
|
|
temp_unit_ptr_->set_hidden(true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Visually moves a unit from the last hex we drew to the one specified by
|
|
* @a path_index. If @a path_index points to an earlier hex, we do nothing.
|
|
* The moving unit will only be updated if update is set to true; otherwise,
|
|
* the provided unit is merely hidden during the movement and re-shown after.
|
|
* (Not updating the unit can produce smoother animations in some cases.)
|
|
* If @a wait is set to false, this returns without waiting for the final
|
|
* animation to finish. Call wait_for_anims() to explicitly get this final
|
|
* wait (another call to proceed_to() or finish() will implicitly wait). The
|
|
* unit must remain valid until the wait is finished.
|
|
*/
|
|
void unit_mover::proceed_to(unit_ptr u, size_t path_index, bool update, bool wait)
|
|
{
|
|
// Nothing to do here if animations cannot be shown.
|
|
if ( !can_draw_ || !animate_ )
|
|
return;
|
|
|
|
// Handle pending visibility issues before introducing new ones.
|
|
wait_for_anims();
|
|
|
|
if ( update || !temp_unit_ptr_ )
|
|
// Replace the temp unit (which also hides u and shows our temporary).
|
|
replace_temporary(u);
|
|
else
|
|
{
|
|
// Just switch the display from the real unit to our fake one.
|
|
temp_unit_ptr_->set_hidden(false);
|
|
u->set_hidden(true);
|
|
}
|
|
|
|
// Safety check.
|
|
path_index = std::min(path_index, path_.size()-1);
|
|
|
|
for ( ; current_ < path_index; ++current_ )
|
|
// If the unit can be seen by the viewing side while making this step:
|
|
if ( !is_enemy_ || !temp_unit_ptr_->invisible(path_[current_]) ||
|
|
!temp_unit_ptr_->invisible(path_[current_+1]) )
|
|
{
|
|
// Wait for the previous step to complete before drawing the next one.
|
|
wait_for_anims();
|
|
|
|
if ( !disp_->tile_fully_on_screen(path_[current_]) ||
|
|
!disp_->tile_fully_on_screen(path_[current_+1]))
|
|
{
|
|
// prevent the unit from disappearing if we scroll here with i == 0
|
|
temp_unit_ptr_->set_location(path_[current_]);
|
|
disp_->invalidate(path_[current_]);
|
|
// scroll in as much of the remaining path as possible
|
|
if ( temp_unit_ptr_->anim_comp().get_animation() )
|
|
temp_unit_ptr_->anim_comp().get_animation()->pause_animation();
|
|
disp_->scroll_to_tiles(path_.begin() + current_,
|
|
path_.end(), game_display::ONSCREEN,
|
|
true, false, 0.0, force_scroll_);
|
|
if ( temp_unit_ptr_->anim_comp().get_animation() )
|
|
temp_unit_ptr_->anim_comp().get_animation()->restart_animation();
|
|
}
|
|
|
|
if ( tiles_adjacent(path_[current_], path_[current_+1]) )
|
|
wait_until_ =
|
|
move_unit_between(path_[current_], path_[current_+1],
|
|
temp_unit_ptr_.get_unit_ptr(), current_,
|
|
path_.size() - (current_+2), animator_,
|
|
*disp_);
|
|
else if ( path_[current_] != path_[current_+1] )
|
|
teleport_unit_between(path_[current_], path_[current_+1],
|
|
*temp_unit_ptr_, *disp_);
|
|
}
|
|
|
|
// Update the unit's facing.
|
|
u->set_facing(temp_unit_ptr_->facing());
|
|
u->anim_comp().set_standing(false); // Need to reset u's animation so the new facing takes effect.
|
|
// Remember the unit to unhide when the animation finishes.
|
|
shown_unit_ = u;
|
|
if ( wait )
|
|
wait_for_anims();
|
|
}
|
|
|
|
|
|
/**
|
|
* Waits for the final animation of the most recent proceed_to() to finish.
|
|
* It is not necessary to call this unless you want to wait before the next
|
|
* call to proceed_to() or finish().
|
|
*/
|
|
void unit_mover::wait_for_anims()
|
|
{
|
|
if ( wait_until_ == INT_MAX )
|
|
// Wait for end (not currently used, but still supported).
|
|
animator_.wait_for_end();
|
|
else if ( wait_until_ != INT_MIN ) {
|
|
// Wait until the specified time (used for normal movement).
|
|
animator_.wait_until(wait_until_);
|
|
// debug code, see unit_frame::redraw()
|
|
// std::cout << " end\n";
|
|
}
|
|
|
|
// Reset data.
|
|
wait_until_ = INT_MIN;
|
|
animator_.clear();
|
|
|
|
update_shown_unit();
|
|
}
|
|
|
|
|
|
/**
|
|
* Finishes the display of movement for the supplied unit.
|
|
* If called before showing the unit reach the end of the path, it will be
|
|
* assumed that the movement ended early.
|
|
* If @a dir is not supplied, the final direction will be determined by (the
|
|
* last two traversed hexes of) the path.
|
|
*/
|
|
void unit_mover::finish(unit_ptr u, map_location::DIRECTION dir)
|
|
{
|
|
// Nothing to do here if the display is not valid.
|
|
if ( !can_draw_ )
|
|
return;
|
|
|
|
const map_location & end_loc = path_[current_];
|
|
const map_location::DIRECTION final_dir = current_ == 0 ?
|
|
path_[0].get_relative_dir(path_[1]) :
|
|
path_[current_-1].get_relative_dir(end_loc);
|
|
|
|
if ( animate_ )
|
|
{
|
|
wait_for_anims(); // In case proceed_to() did not wait for the last animation.
|
|
|
|
// Make sure the displayed unit is correct.
|
|
replace_temporary(u);
|
|
temp_unit_ptr_->set_location(end_loc);
|
|
temp_unit_ptr_->set_facing(final_dir);
|
|
|
|
// Animation
|
|
animator_.add_animation(temp_unit_ptr_.get(), "post_movement", end_loc);
|
|
animator_.start_animations();
|
|
animator_.wait_for_end();
|
|
animator_.clear();
|
|
|
|
// Switch the display back to the real unit.
|
|
u->set_hidden(was_hidden_);
|
|
temp_unit_ptr_->set_hidden(true);
|
|
|
|
events::mouse_handler* mousehandler = events::mouse_handler::get_singleton();
|
|
if ( mousehandler ) {
|
|
mousehandler->invalidate_reachmap();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Show the unit at end of skipped animation
|
|
u->set_hidden(was_hidden_);
|
|
}
|
|
|
|
// Facing gets set even when not animating.
|
|
u->set_facing(dir == map_location::NDIRECTIONS ? final_dir : dir);
|
|
u->anim_comp().set_standing(true); // Need to reset u's animation so the new facing takes effect.
|
|
|
|
// Redraw path ends (even if not animating).
|
|
disp_->invalidate(path_.front());
|
|
disp_->invalidate(end_loc);
|
|
}
|
|
|
|
|
|
/**
|
|
* Display a unit moving along a given path.
|
|
*
|
|
* @param path The path to traverse.
|
|
* @param u The unit to show being moved. Its facing will be updated,
|
|
* but not its position.
|
|
* @param animate If set to false, only side-effects of move are applied
|
|
* (correct unit facing, path hexes redrawing).
|
|
* @param dir Unit will be set facing this direction after move.
|
|
* If nothing passed, direction will be set based on path.
|
|
*/
|
|
/* Note: Hide the unit in its current location,
|
|
* but don't actually remove it until the move is done,
|
|
* so that while the unit is moving status etc.
|
|
* will still display the correct number of units.
|
|
*/
|
|
void move_unit(const std::vector<map_location>& path, unit_ptr u,
|
|
bool animate, map_location::DIRECTION dir,
|
|
bool force_scroll)
|
|
{
|
|
unit_mover mover(path, animate, force_scroll);
|
|
|
|
mover.start(u);
|
|
mover.proceed_to(u, path.size());
|
|
mover.finish(u, dir);
|
|
}
|
|
|
|
|
|
void reset_helpers(const unit *attacker,const unit *defender);
|
|
|
|
void unit_draw_weapon(const map_location& loc, unit& attacker,
|
|
const attack_type* attack,const attack_type* secondary_attack, const map_location& defender_loc,unit* defender)
|
|
{
|
|
display* disp = display::get_singleton();
|
|
if(!disp ||disp->video().update_locked() || disp->video().faked() || disp->fogged(loc) || preferences::show_combat() == false) {
|
|
return;
|
|
}
|
|
unit_animator animator;
|
|
animator.add_animation(&attacker,"draw_weapon",loc,defender_loc,0,false,"",0,unit_animation::HIT,attack,secondary_attack,0);
|
|
animator.add_animation(defender,"draw_weapon",defender_loc,loc,0,false,"",0,unit_animation::MISS,secondary_attack,attack,0);
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
|
|
}
|
|
|
|
|
|
void unit_sheath_weapon(const map_location& primary_loc, unit* primary_unit,
|
|
const attack_type* primary_attack,const attack_type* secondary_attack, const map_location& secondary_loc,unit* secondary_unit)
|
|
{
|
|
display* disp = display::get_singleton();
|
|
if(!disp ||disp->video().update_locked() || disp->video().faked() || disp->fogged(primary_loc) || preferences::show_combat() == false) {
|
|
return;
|
|
}
|
|
unit_animator animator;
|
|
if(primary_unit) {
|
|
animator.add_animation(primary_unit,"sheath_weapon",primary_loc,secondary_loc,0,false,"",0,unit_animation::INVALID,primary_attack,secondary_attack,0);
|
|
}
|
|
if(secondary_unit) {
|
|
animator.add_animation(secondary_unit,"sheath_weapon",secondary_loc,primary_loc,0,false,"",0,unit_animation::INVALID,secondary_attack,primary_attack,0);
|
|
}
|
|
|
|
if(primary_unit || secondary_unit) {
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
}
|
|
if(primary_unit) {
|
|
primary_unit->anim_comp().set_standing();
|
|
}
|
|
if(secondary_unit) {
|
|
secondary_unit->anim_comp().set_standing();
|
|
}
|
|
reset_helpers(primary_unit,secondary_unit);
|
|
|
|
}
|
|
|
|
|
|
void unit_die(const map_location& loc, unit& loser,
|
|
const attack_type* attack,const attack_type* secondary_attack, const map_location& winner_loc,unit* winner)
|
|
{
|
|
display* disp = display::get_singleton();
|
|
if(!disp ||disp->video().update_locked() || disp->video().faked() || disp->fogged(loc) || preferences::show_combat() == false) {
|
|
return;
|
|
}
|
|
unit_animator animator;
|
|
// hide the hp/xp bars of the loser (useless and prevent bars around an erased unit)
|
|
animator.add_animation(&loser,"death",loc,winner_loc,0,false,"",0,unit_animation::KILL,attack,secondary_attack,0);
|
|
// but show the bars of the winner (avoid blinking and show its xp gain)
|
|
animator.add_animation(winner,"victory",winner_loc,loc,0,true,"",0,
|
|
unit_animation::KILL,secondary_attack,attack,0);
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
|
|
reset_helpers(winner,&loser);
|
|
events::mouse_handler* mousehandler = events::mouse_handler::get_singleton();
|
|
if (mousehandler) {
|
|
mousehandler->invalidate_reachmap();
|
|
}
|
|
}
|
|
|
|
|
|
void unit_attack(display * disp, game_board & board,
|
|
const map_location& a, const map_location& b, int damage,
|
|
const attack_type& attack, const attack_type* secondary_attack,
|
|
int swing,std::string hit_text,int drain_amount,std::string att_text)
|
|
{
|
|
if(!disp ||disp->video().update_locked() || disp->video().faked() ||
|
|
(disp->fogged(a) && disp->fogged(b)) || preferences::show_combat() == false) {
|
|
return;
|
|
}
|
|
//const unit_map& units = disp->get_units();
|
|
disp->select_hex(map_location::null_location());
|
|
|
|
// scroll such that there is at least half a hex spacing around fighters
|
|
disp->scroll_to_tiles(a,b,game_display::ONSCREEN,true,0.5,false);
|
|
|
|
log_scope("unit_attack");
|
|
|
|
const unit_map::const_iterator att = board.units().find(a);
|
|
assert(att.valid());
|
|
const unit& attacker = *att;
|
|
|
|
const unit_map::iterator def = board.find_unit(b);
|
|
assert(def.valid());
|
|
unit &defender = *def;
|
|
int def_hitpoints = defender.hitpoints();
|
|
|
|
att->set_facing(a.get_relative_dir(b));
|
|
def->set_facing(b.get_relative_dir(a));
|
|
defender.set_facing(b.get_relative_dir(a));
|
|
|
|
|
|
unit_animator animator;
|
|
unit_ability_list leaders = attacker.get_abilities("leadership");
|
|
unit_ability_list helpers = defender.get_abilities("resistance");
|
|
|
|
std::string text = number_and_text(damage, hit_text);
|
|
std::string text_2 = number_and_text(abs(drain_amount), att_text);
|
|
|
|
unit_animation::hit_type hit_type;
|
|
if(damage >= defender.hitpoints()) {
|
|
hit_type = unit_animation::KILL;
|
|
} else if(damage > 0) {
|
|
hit_type = unit_animation::HIT;
|
|
}else {
|
|
hit_type = unit_animation::MISS;
|
|
}
|
|
animator.add_animation(&attacker, "attack", att->get_location(),
|
|
def->get_location(), damage, true, text_2,
|
|
(drain_amount >= 0) ? display::rgb(0, 255, 0) : display::rgb(255, 0, 0),
|
|
hit_type, &attack, secondary_attack, swing);
|
|
|
|
// note that we take an anim from the real unit, we'll use it later
|
|
const unit_animation *defender_anim = def->anim_comp().choose_animation(*disp,
|
|
def->get_location(), "defend", att->get_location(), damage,
|
|
hit_type, &attack, secondary_attack, swing);
|
|
animator.add_animation(&defender, defender_anim, def->get_location(),
|
|
true, text , display::rgb(255, 0, 0));
|
|
|
|
BOOST_FOREACH (const unit_ability & ability, leaders) {
|
|
if(ability.second == a) continue;
|
|
if(ability.second == b) continue;
|
|
unit_map::const_iterator leader = board.units().find(ability.second);
|
|
assert(leader.valid());
|
|
leader->set_facing(ability.second.get_relative_dir(a));
|
|
animator.add_animation(&*leader, "leading", ability.second,
|
|
att->get_location(), damage, true, "", 0,
|
|
hit_type, &attack, secondary_attack, swing);
|
|
}
|
|
BOOST_FOREACH (const unit_ability & ability, helpers) {
|
|
if(ability.second == a) continue;
|
|
if(ability.second == b) continue;
|
|
unit_map::const_iterator helper = board.units().find(ability.second);
|
|
assert(helper.valid());
|
|
helper->set_facing(ability.second.get_relative_dir(b));
|
|
animator.add_animation(&*helper, "resistance", ability.second,
|
|
def->get_location(), damage, true, "", 0,
|
|
hit_type, &attack, secondary_attack, swing);
|
|
}
|
|
|
|
|
|
animator.start_animations();
|
|
animator.wait_until(0);
|
|
int damage_left = damage;
|
|
while(damage_left > 0 && !animator.would_end()) {
|
|
int step_left = (animator.get_end_time() - animator.get_animation_time() )/50;
|
|
if(step_left < 1) step_left = 1;
|
|
int removed_hp = damage_left/step_left ;
|
|
if(removed_hp < 1) removed_hp = 1;
|
|
defender.take_hit(removed_hp);
|
|
damage_left -= removed_hp;
|
|
animator.wait_until(animator.get_animation_time_potential() +50);
|
|
}
|
|
animator.wait_for_end();
|
|
// pass the animation back to the real unit
|
|
def->anim_comp().start_animation(animator.get_end_time(), defender_anim, true);
|
|
reset_helpers(&*att, &*def);
|
|
def->set_hitpoints(def_hitpoints);
|
|
}
|
|
|
|
// private helper function, set all helpers to default position
|
|
void reset_helpers(const unit *attacker,const unit *defender)
|
|
{
|
|
display* disp = display::get_singleton();
|
|
const unit_map& units = disp->get_units();
|
|
if(attacker) {
|
|
unit_ability_list leaders = attacker->get_abilities("leadership");
|
|
BOOST_FOREACH (const unit_ability & ability, leaders) {
|
|
unit_map::const_iterator leader = units.find(ability.second);
|
|
assert(leader != units.end());
|
|
leader->anim_comp().set_standing();
|
|
}
|
|
}
|
|
|
|
if(defender) {
|
|
unit_ability_list helpers = defender->get_abilities("resistance");
|
|
BOOST_FOREACH (const unit_ability & ability, helpers) {
|
|
unit_map::const_iterator helper = units.find(ability.second);
|
|
assert(helper != units.end());
|
|
helper->anim_comp().set_standing();
|
|
}
|
|
}
|
|
}
|
|
|
|
void unit_recruited(const map_location& loc,const map_location& leader_loc)
|
|
{
|
|
game_display* disp = game_display::get_singleton();
|
|
if(!disp || disp->video().update_locked() || disp->video().faked() ||disp->fogged(loc)) return;
|
|
unit_map::const_iterator u = disp->get_units().find(loc);
|
|
if(u == disp->get_units().end()) return;
|
|
u->set_hidden(true);
|
|
|
|
unit_animator animator;
|
|
if(leader_loc != map_location::null_location()) {
|
|
unit_map::const_iterator leader = disp->get_units().find(leader_loc);
|
|
if(leader == disp->get_units().end()) return;
|
|
disp->scroll_to_tiles(loc,leader_loc,game_display::ONSCREEN,true,0.0,false);
|
|
leader->set_facing(leader_loc.get_relative_dir(loc));
|
|
animator.add_animation(&*leader, "recruiting", leader_loc, loc, 0, true);
|
|
} else {
|
|
disp->scroll_to_tile(loc,game_display::ONSCREEN,true,false);
|
|
}
|
|
|
|
disp->draw();
|
|
u->set_hidden(false);
|
|
animator.add_animation(&*u, "recruited", loc, leader_loc);
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
animator.set_all_standing();
|
|
if (loc==disp->mouseover_hex()) disp->invalidate_unit();
|
|
}
|
|
|
|
void unit_healing(unit &healed, const std::vector<unit *> &healers, int healing,
|
|
const std::string & extra_text)
|
|
{
|
|
game_display* disp = game_display::get_singleton();
|
|
const map_location &healed_loc = healed.get_location();
|
|
if(!disp || disp->video().update_locked() || disp->video().faked() || disp->fogged(healed_loc)) return;
|
|
|
|
// This is all the pretty stuff.
|
|
disp->scroll_to_tile(healed_loc, game_display::ONSCREEN,true,false);
|
|
disp->display_unit_hex(healed_loc);
|
|
unit_animator animator;
|
|
|
|
BOOST_FOREACH(unit *h, healers) {
|
|
h->set_facing(h->get_location().get_relative_dir(healed_loc));
|
|
animator.add_animation(h, "healing", h->get_location(),
|
|
healed_loc, healing);
|
|
}
|
|
|
|
if (healing < 0) {
|
|
animator.add_animation(&healed, "poisoned", healed_loc,
|
|
map_location::null_location(), -healing, false,
|
|
number_and_text(-healing, extra_text),
|
|
display::rgb(255,0,0));
|
|
} else if ( healing > 0 ) {
|
|
animator.add_animation(&healed, "healed", healed_loc,
|
|
map_location::null_location(), healing, false,
|
|
number_and_text(healing, extra_text),
|
|
display::rgb(0,255,0));
|
|
} else {
|
|
animator.add_animation(&healed, "healed", healed_loc,
|
|
map_location::null_location(), 0, false,
|
|
extra_text, display::rgb(0,255,0));
|
|
}
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
animator.set_all_standing();
|
|
}
|
|
|
|
void wml_animation_internal(unit_animator &animator, const vconfig &cfg, const map_location &default_location = map_location::null_location());
|
|
|
|
void wml_animation(const vconfig &cfg, const map_location &default_location)
|
|
{
|
|
game_display &disp = *resources::screen;
|
|
if (disp.video().update_locked() || disp.video().faked()) return;
|
|
unit_animator animator;
|
|
wml_animation_internal(animator, cfg, default_location);
|
|
animator.start_animations();
|
|
animator.wait_for_end();
|
|
animator.set_all_standing();
|
|
}
|
|
|
|
void wml_animation_internal(unit_animator &animator, const vconfig &cfg, const map_location &default_location)
|
|
{
|
|
unit_const_ptr u;
|
|
|
|
unit_map::const_iterator u_it = resources::units->find(default_location);
|
|
if (u_it.valid()) {
|
|
u = u_it.get_shared_ptr();
|
|
}
|
|
|
|
// Search for a valid unit filter,
|
|
// and if we have one, look for the matching unit
|
|
vconfig filter = cfg.child("filter");
|
|
if(!filter.null()) {
|
|
const unit_filter ufilt(filter, resources::filter_con);
|
|
u = ufilt.first_match_on_map();
|
|
}
|
|
|
|
// We have found a unit that matches the filter
|
|
if (u && !resources::screen->fogged(u->get_location()))
|
|
{
|
|
attack_type *primary = NULL;
|
|
attack_type *secondary = NULL;
|
|
Uint32 text_color;
|
|
unit_animation::hit_type hits= unit_animation::INVALID;
|
|
std::vector<attack_type> attacks = u->attacks();
|
|
std::vector<attack_type>::iterator itor;
|
|
|
|
// death and victory animations are handled here because usually
|
|
// the code iterates through all the unit's attacks
|
|
// but in these two specific cases we need to create dummy attacks
|
|
// to fire correctly certain animations
|
|
// this is especially evident with the Wose's death animations
|
|
if (cfg["flag"] == "death" || cfg["flag"] == "victory") {
|
|
filter = cfg.child("primary_attack");
|
|
if(!filter.null()) {
|
|
attack_type dummy_primary = static_cast<attack_type>(filter.get_config());
|
|
primary = &dummy_primary;
|
|
}
|
|
filter = cfg.child("secondary_attack");
|
|
if(!filter.null()) {
|
|
attack_type dummy_secondary = static_cast<attack_type>(filter.get_config());
|
|
secondary = &dummy_secondary;
|
|
}
|
|
}
|
|
|
|
else {
|
|
filter = cfg.child("primary_attack");
|
|
if(!filter.null()) {
|
|
for(itor = attacks.begin(); itor != attacks.end(); ++itor){
|
|
if(itor->matches_filter(filter.get_parsed_config())) {
|
|
primary = &*itor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
filter = cfg.child("secondary_attack");
|
|
if(!filter.null()) {
|
|
for(itor = attacks.begin(); itor != attacks.end(); ++itor){
|
|
if(itor->matches_filter(filter.get_parsed_config())) {
|
|
secondary = &*itor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(cfg["hits"] == "yes" || cfg["hits"] == "hit") {
|
|
hits = unit_animation::HIT;
|
|
}
|
|
if(cfg["hits"] == "no" || cfg["hits"] == "miss") {
|
|
hits = unit_animation::MISS;
|
|
}
|
|
if( cfg["hits"] == "kill" ) {
|
|
hits = unit_animation::KILL;
|
|
}
|
|
if(cfg["red"].empty() && cfg["green"].empty() && cfg["blue"].empty()) {
|
|
text_color = display::rgb(0xff,0xff,0xff);
|
|
} else {
|
|
text_color = display::rgb(cfg["red"], cfg["green"], cfg["blue"]);
|
|
}
|
|
resources::screen->scroll_to_tile(u->get_location(), game_display::ONSCREEN, true, false);
|
|
vconfig t_filter = cfg.child("facing");
|
|
map_location secondary_loc = map_location::null_location();
|
|
if(!t_filter.empty()) {
|
|
terrain_filter filter(t_filter, resources::filter_con);
|
|
std::set<map_location> locs;
|
|
filter.get_locations(locs);
|
|
if (!locs.empty() && u->get_location() != *locs.begin()) {
|
|
map_location::DIRECTION dir =u->get_location().get_relative_dir(*locs.begin());
|
|
u->set_facing(dir);
|
|
secondary_loc = u->get_location().get_direction(dir);
|
|
}
|
|
}
|
|
animator.add_animation(&*u, cfg["flag"], u->get_location(),
|
|
secondary_loc, cfg["value"], cfg["with_bars"].to_bool(),
|
|
cfg["text"], text_color, hits, primary, secondary,
|
|
cfg["value_second"]);
|
|
}
|
|
const vconfig::child_list sub_anims = cfg.get_children("animate");
|
|
vconfig::child_list::const_iterator anim_itor;
|
|
for(anim_itor = sub_anims.begin(); anim_itor != sub_anims.end();++anim_itor) {
|
|
wml_animation_internal(animator, *anim_itor);
|
|
}
|
|
|
|
}
|
|
} // end unit_display namespace
|