/** * 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); }
bool unit::is_ally(const unit_ptr &u) const { if (!u) return false; if (get_align() == align_neutral || u->get_align() == align_neutral) return true; if (u->get_align() == get_align()) return true; if (u->get_align() == align_ally || get_align() == align_ally) return false; return true; }
unit_map::umap_retval_pair_t unit_map::replace(const map_location& l, unit_ptr p) { self_check(); p->set_location(l); erase(l); return insert(p); }
void world::remove_unit(unit_ptr u) { unit_map::iterator pos = units_.find(u->loc()); assert(pos != units_.end()); unit_list::iterator i = std::find(pos->second.begin(), pos->second.end(), u); assert(i != pos->second.end()); pos->second.erase(i); }
/* 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_ == nullptr ) // 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()); }
/** * 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(); }
void world::add_unit(const hex::location& loc, unit_ptr u) { units_[loc].push_back(u); u->set_loc(loc); }
/** Inserts the unit pointed to by @a p into the unit_map. It needs to succeed on the insertion to the umap and to the lmap otherwise all operations are reverted. 1. Construct a unit_pod 2. Try insertion into the umap 3. Try insertion in the lmap and remove the umap entry on failure The one oddity is that to facilitate non-invalidating iterators the list sometimes has NULL pointers which should be used when they correspond to uids previously used. */ std::pair<unit_map::unit_iterator, bool> unit_map::insert(unit_ptr p) { self_check(); assert(p); size_t unit_id = p->underlying_id(); const map_location &loc = p->get_location(); if (!loc.valid()) { ERR_NG << "Trying to add " << p->name() << " - " << p->id() << " at an invalid location; Discarding.\n"; return std::make_pair(make_unit_iterator(umap_.end()), false); } unit_pod upod; upod.unit = p ; DBG_NG << "Adding unit " << p->underlying_id() << " - " << p->id() << " to location: (" << loc << ")\n"; std::pair<t_umap::iterator, bool> uinsert = umap_.insert(std::make_pair(unit_id, upod )); if (! uinsert.second) { //If the pod is empty reinsert the unit in the same list element if (!uinsert.first->second.unit) { unit_pod &opod = uinsert.first->second; opod.unit = p ; assert(opod.ref_count != 0); } else { unit_ptr q = uinsert.first->second.unit; ERR_NG << "Trying to add " << p->name() << " - " << p->id() << " - " << p->underlying_id() << " (" << loc << ") over " << q->name() << " - " << q->id() << " - " << q->underlying_id() << " (" << q->get_location() << ")."; p->clone(false); ERR_NG << "The new unit was assigned underlying_id=" << p->underlying_id() << " to prevent duplicate id conflicts.\n"; uinsert = umap_.insert(std::make_pair(p->underlying_id(), upod )); int guard(0); while (!uinsert.second && (++guard < 1e6) ) { if(guard % 10 == 9){ ERR_NG << "\n\nPlease Report this error to https://gna.org/bugs/index.php?18591 " "\nIn addition to the standard details of operating system and wesnoth version " "and how it happened, please answer the following questions " "\n 1. Were you playing multi-player?" "\n 2. Did you start/restart/reload the game/scenario?" "\nThank you for your help in fixing this bug.\n"; } p->clone(false); uinsert = umap_.insert(std::make_pair(p->underlying_id(), upod )); } if (!uinsert.second) { throw game::error("One million collisions in unit_map"); } } } std::pair<t_lmap::iterator,bool> linsert = lmap_.insert(std::make_pair(loc, uinsert.first )); //Fail if the location is occupied if(! linsert.second) { if(upod.ref_count == 0) { //Undo a virgin insertion umap_.erase(uinsert.first); } else { //undo a reinsertion uinsert.first->second.unit.reset(); } DBG_NG << "Trying to add " << p->name() << " - " << p->id() << " at location ("<<loc <<"); Occupied by " <<(linsert.first->second->second).unit->name()<< " - " << linsert.first->second->second.unit->id() <<"\n"; return std::make_pair(make_unit_iterator(umap_.end()), false); } self_check(); return std::make_pair( make_unit_iterator( uinsert.first ), true); }
static bool find_if_matches_uid_helper(const unit_ptr & ptr, size_t uid) { return ptr->underlying_id() == uid; }
static bool find_if_matches_helper(const unit_ptr & ptr, const std::string & unit_id) { return ptr->matches_id(unit_id); }
bool game_board::try_add_unit_to_recall_list(const map_location&, const unit_ptr u) { teams_[u->side()-1].recall_list().add(u); return true; }