/** * 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::hit_type::INVALID,nullptr,nullptr,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; }
/** * 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); }
/** * 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_ ) { // Make sure to reset the unit's animation to deal with a quirk in the // action engine where it leaves it to us to reenable bars even if the // display is initially locked. u->anim_comp().set_standing(true); 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); }
/** * 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(); }