Logo Search packages:      
Sourcecode: wesnoth-1.9 version File versions  Download package

mouse_events.cpp

/* $Id: mouse_events.cpp 46186 2010-09-01 21:12:38Z silene $ */
/*
   Copyright (C) 2006 - 2010 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
   wesnoth playturn Copyright (C) 2003 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.
*/

#include "global.hpp"

#include "mouse_events.hpp"

#include "actions.hpp"
#include "attack_prediction_display.hpp"
#include "dialogs.hpp"
#include "foreach.hpp"
#include "game_end_exceptions.hpp"
#include "game_events.hpp"
#include "gettext.hpp"
#include "gui/dialogs/unit_attack.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "gui/widgets/window.hpp"
#include "log.hpp"
#include "map.hpp"
#include "marked-up_text.hpp"
#include "menu_events.hpp"
#include "play_controller.hpp"
#include "sound.hpp"
#include "replay.hpp"
#include "resources.hpp"
#include "rng.hpp"
#include "tod_manager.hpp"
#include "wml_separators.hpp"
#include "whiteboard/manager.hpp"

#include <boost/bind.hpp>

static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)

namespace events{


mouse_handler::mouse_handler(game_display* gui, std::vector<team>& teams,
            unit_map& units, gamemap& map, tod_manager& tod_mng) :
      mouse_handler_base(),
      map_(map),
      gui_(gui),
      teams_(teams),
      units_(units),
      tod_manager_(tod_mng),
      previous_hex_(),
      previous_free_hex_(),
      selected_hex_(),
      next_unit_(),
      current_route_(),
      waypoints_(),
      current_paths_(),
      enemy_paths_(false),
      path_turns_(0),
      side_num_(1),
      undo_(false),
      over_route_(false),
      reachmap_invalid_(false),
      show_partial_move_(false)
{
      singleton_ = this;
}

mouse_handler::~mouse_handler()
{
      rand_rng::clear_new_seed_callback();
      singleton_ = NULL;
}

void mouse_handler::set_side(int side_number)
{
      side_num_ = side_number;
}

int mouse_handler::drag_threshold() const
{
      return 14;
}

void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update)
{
      // we ignore the position coming from event handler
      // because it's always a little obsolete and we don't need
      // to hightlight all the hexes where the mouse passed.
      // Also, sometimes it seems to have one *very* obsolete
      // and isolated mouse motion event when using drag&drop
      SDL_GetMouseState(&x,&y);  // <-- modify x and y

      if (mouse_handler_base::mouse_motion_default(x, y, update)) return;

      const map_location new_hex = gui().hex_clicked_on(x,y);

      if(new_hex != last_hex_) {
            update = true;
            if (last_hex_.valid()) {
                  // we store the previous hexes used to propose attack direction
                  previous_hex_ = last_hex_;
                  // the hex of the selected unit is also "free"
                  { // start planned pathfind map scope
                        wb::scoped_planned_pathfind_map planned_pathfind_map;
                        if (last_hex_ == selected_hex_ || find_unit(last_hex_) == units_.end()) {
                              previous_free_hex_ = last_hex_;
                        }
                  } // end planned pathfind map scope
            }
            last_hex_ = new_hex;
      }


      if (reachmap_invalid_) update = true;

      if (update) {
            if (reachmap_invalid_) {
                  reachmap_invalid_ = false;
                  if (!current_paths_.destinations.empty() && !show_partial_move_) {
                        unit_map::iterator u;
                        { // start planned unit map scope
                              wb::scoped_planned_unit_map planned_unit_map;
                               u = find_unit(selected_hex_);
                        } // end planned unit map scope
                        if(selected_hex_.valid() && u != units_.end() ) {
                              // reselect the unit without firing events (updates current_paths_)
                              select_hex(selected_hex_, true);
                        }
                        // we do never deselect here, mainly because of canceled attack-move
                  }
            }

            // reset current_route_ and current_paths if not valid anymore
            // we do it before cursor selection, because it uses current_paths_
            if(new_hex.valid() == false) {
                  current_route_.steps.clear();
                  gui().set_route(NULL);
                  resources::whiteboard->erase_temp_move();
            }

            if(enemy_paths_) {
                  enemy_paths_ = false;
                  current_paths_ = pathfind::paths();
                  gui().unhighlight_reach();
            } else if(over_route_) {
                  over_route_ = false;
                  current_route_.steps.clear();
                  gui().set_route(NULL);
                  resources::whiteboard->erase_temp_move();
            }

            gui().highlight_hex(new_hex);
            resources::whiteboard->on_mouseover_change(new_hex);

            unit_map::iterator selected_unit;
            unit_map::iterator mouseover_unit;
            map_location attack_from;

            { // start planned unit map scope
                  wb::scoped_planned_unit_map planned_unit_map;
                  selected_unit = find_unit(selected_hex_);
                  mouseover_unit = find_unit(new_hex);

                  // we search if there is an attack possibility and where
                  attack_from = current_unit_attacks_from(new_hex);

                  //see if we should show the normal cursor, the movement cursor, or
                  //the attack cursor
                  //If the cursor is on WAIT, we don't change it and let the setter
                  //of this state end it
                  if (cursor::get() != cursor::WAIT) {
                        if (selected_unit != units_.end() &&
                              selected_unit->side() == side_num_ &&
                              !selected_unit->incapacitated() && !browse)
                        {
                              if (attack_from.valid()) {
                                    cursor::set(dragging_started_ ? cursor::ATTACK_DRAG : cursor::ATTACK);
                              }
                              else if (mouseover_unit==units_.end() &&
                                           current_paths_.destinations.contains(new_hex))
                              {
                                    cursor::set(dragging_started_ ? cursor::MOVE_DRAG : cursor::MOVE);
                              } else {
                                    // selected unit can't attack or move there
                                    cursor::set(cursor::NORMAL);
                              }
                        } else {
                              // no selected unit or we can't move it
                              cursor::set(cursor::NORMAL);
                        }
                  }
            } // end planned unit map scope

            // show (or cancel) the attack direction indicator
            if (attack_from.valid() && (!browse || resources::whiteboard->is_active())) {
                  gui().set_attack_indicator(attack_from, new_hex);
            } else {
                  gui().clear_attack_indicator();
            }

            // the destination is the pointed hex or the adjacent hex
            // used to attack it
            map_location dest;
            unit_map::const_iterator dest_un;
            { // start planned pathfind map scope
                  wb::scoped_planned_pathfind_map planned_pathfind_map;
                  if (attack_from.valid()) {
                        dest = attack_from;
                        dest_un = find_unit(dest);
                  }     else {
                        dest = new_hex;
                        dest_un = find_unit(new_hex);
                  }
            } // end planned pathfind map scope

            if(dest == selected_hex_ || dest_un != units_.end()) {
                  current_route_.steps.clear();
                  gui().set_route(NULL);
                  resources::whiteboard->erase_temp_move();
            }
            else if (!current_paths_.destinations.empty() &&
                         map_.on_board(selected_hex_) && map_.on_board(new_hex))
            {
                  if (selected_unit != units_.end() && !selected_unit->incapacitated()) {

                        // Show the route from selected unit to mouseover hex
                        // the movement_reset is active only if it's not the unit's turn
                        // Note: we should activate the whiteboard's planned unit map only after this is done,
                        // since the future state includes changes to the units' movement points
                        unit_movement_resetter move_reset(*selected_unit,
                                    selected_unit->side() != side_num_);

                        { wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
                              current_route_ = get_route(&*selected_unit, dest, waypoints_, viewing_team());
                        } // end planned pathfind map scope

                        resources::whiteboard->create_temp_move();

                        if(!browse) {
                              gui().set_route(&current_route_);
                        }
                  }
            }

            unit* un;
            { // start planned unit map scope
                  wb::scoped_planned_unit_map planned_unit_map;
                  unit_map::iterator iter = mouseover_unit;
                  if (iter != units_.end())
                        un = &*iter;
                  else
                        un = NULL;
            } //end planned unit map scope

            if (un && current_paths_.destinations.empty() &&
                  !gui().fogged(un->get_location()))
            {
                  if (un->side() != side_num_) {
                        //unit under cursor is not on our team, highlight reach
                        //Note: planned unit map must be activated after this is done,
                        //since the future state includes changes to units' movement.
                        unit_movement_resetter move_reset(*un);

                        bool teleport = un->get_ability_bool("teleport");

                        { // start planned unit map scope
                              wb::scoped_planned_pathfind_map planned_pathfind_map;
                              current_paths_ = pathfind::paths(map_,units_,new_hex,teams_,
                                                                                    false,teleport,viewing_team(),path_turns_);
                        } // end planned unit map scope

                        gui().highlight_reach(current_paths_);
                        enemy_paths_ = true;
                  } else {
                        //unit is on our team, show path if the unit has one
                        const map_location go_to = un->get_goto();
                        if(map_.on_board(go_to)) {
                              pathfind::marked_route route;
                              { // start planned unit map scope
                                    wb::scoped_planned_pathfind_map planned_pathfind_map;
                                    route = get_route(un, go_to, un->waypoints(), current_team());
                              } // end planned unit map scope
                              gui().set_route(&route);
                        }
                        over_route_ = true;
                  }
            }
      }
}

unit_map::iterator mouse_handler::selected_unit()
{
      unit_map::iterator res = find_unit(selected_hex_);
      if(res != units_.end()) {
            return res;
      } else {
            return find_unit(last_hex_);
      }
}

unit_map::iterator mouse_handler::find_unit(const map_location& hex)
{
      unit_map::iterator it = find_visible_unit(hex, viewing_team());
      if (it.valid())
            return it;
      else
            return resources::units->end();
}

unit_map::const_iterator mouse_handler::find_unit(const map_location& hex) const
{
      return find_visible_unit(hex, viewing_team());
}

map_location mouse_handler::current_unit_attacks_from(const map_location& loc)
{
      if(loc == selected_hex_)
            return map_location();

      bool wb_active = resources::whiteboard->is_active();

      {
            const unit_map::const_iterator current = find_unit(selected_hex_);
            bool eligible = current != units_.end();
            if (!eligible) return map_location();

            eligible &= current->side() == side_num_ ||
                        (wb_active && current->side() == resources::screen->viewing_side());
            eligible &= current->attacks_left() != 0;
            if (!eligible) return map_location();
      }

      {
            team viewing_team = (*resources::teams)[resources::screen->viewing_team()];

            const unit_map::const_iterator enemy = find_unit(loc);
            bool eligible = enemy != units_.end();
            if (!eligible) return map_location();

            bool show_for_whiteboard = wb_active &&
                        viewing_team.is_enemy(enemy->side());
            eligible &= show_for_whiteboard || current_team().is_enemy(enemy->side());
            eligible &= !enemy->incapacitated();
            if (!eligible) return map_location();
      }

      const map_location::DIRECTION preferred = loc.get_relative_dir(previous_hex_);
      const map_location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);

      int best_rating = 100;//smaller is better
      map_location res;
      map_location adj[6];
      get_adjacent_tiles(loc,adj);

      for(size_t n = 0; n != 6; ++n) {
            if(map_.on_board(adj[n]) == false) {
                  continue;
            }

            if(adj[n] != selected_hex_ && find_unit(adj[n]) != units_.end()) {
                  continue;
            }

            if (current_paths_.destinations.contains(adj[n]))
            {
                  static const size_t NDIRECTIONS = map_location::NDIRECTIONS;
                  unsigned int difference = abs(int(preferred - n));
                  if(difference > NDIRECTIONS/2) {
                        difference = NDIRECTIONS - difference;
                  }
                  unsigned int second_difference = abs(int(second_preferred - n));
                  if(second_difference > NDIRECTIONS/2) {
                        second_difference = NDIRECTIONS - second_difference;
                  }
                  const int rating = difference * 2 + (second_difference > difference);
                  if(rating < best_rating || res.valid() == false) {
                        best_rating = rating;
                        res = adj[n];
                  }
            }
      }

      return res;
}

void mouse_handler::add_waypoint(const map_location& loc) {
      std::vector<map_location>::iterator w = std::find(waypoints_.begin(), waypoints_.end(), loc);
      //toggle between add a new one and remove an old one
      if(w != waypoints_.end()){
            waypoints_.erase(w);
      } else {
            waypoints_.push_back(loc);
      }

      // we need to update the route, simulate a mouse move for the moment
      // (browse is supposed false here, 0,0 are dummy values)
      mouse_motion(0,0, false, true);
}

pathfind::marked_route mouse_handler::get_route(unit* un, map_location go_to, const std::vector<map_location>& waypoints, team &team)
{
      // The pathfinder will check unit visibility (fogged/stealthy).
      const pathfind::shortest_path_calculator calc(*un, team, units_, teams_, map_);

      std::set<map_location> allowed_teleports = pathfind::get_teleport_locations(*un, viewing_team());

      pathfind::plain_route route;

      if (waypoints.empty()) {
            // standard shortest path
            route = pathfind::a_star_search(un->get_location(), go_to, 10000.0, &calc, map_.w(), map_.h(), &allowed_teleports);
      } else {
            // initialize the main route with the first step
            route.steps.push_back(un->get_location());
            route.move_cost = 0;

            //copy waypoints and add first source and last destination
            //TODO: don't copy but use vector index trick
            std::vector<map_location> waypts;
            waypts.push_back(un->get_location());
            waypts.insert(waypts.end(), waypoints.begin(), waypoints.end());
            waypts.push_back(go_to);

            std::vector<map_location>::iterator src = waypts.begin(),
                  dst = ++waypts.begin();
            for(; dst != waypts.end(); ++src,++dst){
                  if (*src == *dst) continue;
                  pathfind::plain_route inter_route = pathfind::a_star_search(*src, *dst, 10000.0, &calc, map_.w(), map_.h(), &allowed_teleports);
                  if(inter_route.steps.size()>=1) {
                        // add to the main route but skip the head (already in)
                        route.steps.insert(route.steps.end(),
                              inter_route.steps.begin()+1,inter_route.steps.end());
                        route.move_cost+=inter_route.move_cost;
                  } else {
                        // we can't reach dst, stop the route at the last src
                        // as the normal case do
                        break;
                  }
            }
      }

      return mark_route(route, waypoints);
}

void mouse_handler::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
{
      mouse_handler_base::mouse_press(event, browse);
}

bool mouse_handler::right_click_show_menu(int x, int y, const bool browse)
{
      // The first right-click cancel the selection if any,
      // the second open the context menu
      unit_map::iterator unit;
      {
            wb::scoped_planned_unit_map wb_modifiers;
            unit = find_unit(selected_hex_);
      }
      if (selected_hex_.valid() && unit != units_.end()) {
            select_hex(map_location(), browse);
            return false;
      } else {
            return point_in_rect(x, y, gui().map_area());
      }
}

bool mouse_handler::left_click(int x, int y, const bool browse)
{
      undo_ = false;
      if (mouse_handler_base::left_click(x, y, browse)) return false;

      //we use the last registered highlighted hex
      //since it's what update our global state
      map_location hex = last_hex_;

      unit_map::iterator u;
      unit_map::iterator clicked_u;
      map_location src;
      pathfind::paths orig_paths;
      map_location attack_from;
      { // start planned unit map scope
            wb::scoped_planned_unit_map planned_unit_map;
            u = find_unit(selected_hex_);

            //if the unit is selected and then itself clicked on,
            //any goto command and waypoints are cancelled
            if (u != units_.end() && !browse && selected_hex_ == hex && u->side() == side_num_) {
                  u->set_goto(map_location());
                  u->waypoints().clear();
                  waypoints_.clear();
            }

            clicked_u = find_unit(hex);

            src = selected_hex_;
            orig_paths = current_paths_;
            attack_from = current_unit_attacks_from(hex);
      } // end planned unit map scope

      //see if we're trying to do a attack or move-and-attack
      if(((!browse && !commands_disabled) || resources::whiteboard->is_active()) && attack_from.valid()) {
            if (resources::whiteboard->is_active() && clicked_u.valid()) {
                  // Unselect the current hex, and create planned attack for whiteboard
                  selected_hex_ = map_location();
                  gui().select_hex(map_location());
                  gui().clear_attack_indicator();
                  gui().set_route(NULL);
                  waypoints_.clear();
                  show_partial_move_ = false;
                  gui().unhighlight_reach();
                  current_paths_ = pathfind::paths();
                  current_route_.steps.clear();

                  resources::whiteboard->save_temp_attack(attack_from, clicked_u->get_location());
                  return false;

            } else if (u.valid() && clicked_u.valid() && u->side() == side_num_) {
                  if (attack_from == selected_hex_) { //no move needed
                        int choice = show_attack_dialog(attack_from, clicked_u->get_location());
                        if (choice >=0 ) {
                              attack_enemy(u->get_location(), clicked_u->get_location(), choice);
                        }
                        return false;
                  }
                  else {
                        // we will now temporary move next to the enemy
                        pathfind::paths::dest_vect::const_iterator itor =
                                    current_paths_.destinations.find(attack_from);
                        if(itor == current_paths_.destinations.end()) {
                              // can't reach the attacking location
                              // not supposed to happen, so abort
                              return false;
                        }
                        // update movement_left as if we did the move
                        int move_left_dst = itor->move_left;
                        int move_left_src = u->movement_left();
                        u->set_movement(move_left_dst);

                        int choice = -1;
                        // block where we temporary move the unit
                        {
                              temporary_unit_mover temp_mover(units_, src, attack_from);
                              choice = show_attack_dialog(attack_from, clicked_u->get_location());
                        }
                        // restore unit as before
                        u = units_.find(src);
                        u->set_movement(move_left_src);
                        u->set_standing();

                        if (choice < 0) {
                              // user hit cancel, don't start move+attack
                              return false;
                        }

                        //register the mouse-UI waypoints into the unit's waypoints
                        u->waypoints() = waypoints_;

                        // store side, since u may be invalidated later
                        int side = u->side();
                        //record visible enemies adjacent to destination
                        std::set<map_location> adj_enemies = get_adj_enemies(attack_from, side);

                        // move the unit without clearing fog (to avoid interruption)
                        //TODO: clear fog and interrupt+resume move
                        if(!move_unit_along_current_route(false)) {
                              // interrupted move
                              // we assume that move_unit() did the cleaning
                              // (update shroud/fog, clear undo if needed)
                              return false;
                        }

                        //check if new enemies are now visible
                        if(get_adj_enemies(attack_from, side) != adj_enemies)
                              return false; //ambush, interrupt attack

                        attack_enemy(attack_from, hex, choice); // Fight !!
                        return false;
                  }
            }
      }
      //otherwise we're trying to move to a hex
      else if(((!commands_disabled && !browse) || resources::whiteboard->is_active()) &&
                  selected_hex_.valid() && selected_hex_ != hex &&
               u != units_.end() && u.valid() &&
               (u->side() == side_num_ || resources::whiteboard->is_active()) &&
                 clicked_u == units_.end() &&
                 !current_route_.steps.empty() &&
                 current_route_.steps.front() == selected_hex_) {

            gui().unhighlight_reach();

            // If the whiteboard is active, it intercepts any unit movement
            if (resources::whiteboard->is_active()) {
                        // Unselect the current hex, and create planned move for whiteboard
                        selected_hex_ = map_location();
                        gui().select_hex(map_location());
                        gui().clear_attack_indicator();
                        gui().set_route(NULL);
                        waypoints_.clear();
                        show_partial_move_ = false;
                        gui().unhighlight_reach();
                        current_paths_ = pathfind::paths();
                        current_route_.steps.clear();

                        resources::whiteboard->save_temp_move();

            // Otherwise proceed to normal unit movement
            } else {
                  //Don't move if the selected unit already has actions
                  //from the whiteboard.
                  if (resources::whiteboard->unit_has_actions(&*u)) {
                        return false;
                  }

                  //If this is a leader on a keep, ask permission to the whiteboard to move it
                  //since otherwise it may cause planned recruits to be erased.
                  if (u->can_recruit() && u->side() == gui().viewing_side() &&
                        resources::game_map->is_keep(u->get_location()) &&
                        !resources::whiteboard->allow_leader_to_move(*u))
                  {
                        gui2::show_transient_message(gui_->video(), "",
                                    _("You cannot move your leader away from the keep with some planned recruits left."));
                        return false;
                  }

                  //register the mouse-UI waypoints into the unit's waypoints
                  u->waypoints() = waypoints_;

                  move_unit_along_current_route(current_team().auto_shroud_updates());
                  // during the move, we may have selected another unit
                  // (but without triggering a select event (command was disabled)
                  // in that case reselect it now to fire the event (+ anim & sound)
                  if (selected_hex_ != src) {
                        select_hex(selected_hex_, browse);
                  }
            }
            return false;
      } else {
            // we select a (maybe empty) hex
            // we block selection during attack+move (because motion is blocked)
            select_hex(hex, browse);
      }
      return false;
      //FIXME: clean all these "return false"
}

void mouse_handler::select_hex(const map_location& hex, const bool browse) {
      selected_hex_ = hex;
      gui().select_hex(hex);
      gui().clear_attack_indicator();
      gui().set_route(NULL);
      waypoints_.clear();
      show_partial_move_ = false;

      wb::scoped_planned_unit_map planned_unit_map; //lasts for whole method

      unit_map::iterator u = find_unit(hex);

      if (hex.valid() && u != units_.end() && u.valid() && !u->get_hidden()) {

            next_unit_ = u->get_location();

            {
                  // if it's not the unit's turn, we reset its moves
                  // and we restore them before the "select" event is raised
                  // Ensure the planned unit map is reapplied afterwards, otherwise it screws up the future state
                  { // start enforced real unit map scope
                        wb::scoped_real_unit_map real_unit_map;
                        unit_movement_resetter move_reset(*u, u->side() != side_num_);
                  } // end enforced real unit map scope
                  bool teleport = u->get_ability_bool("teleport");

                  current_paths_ = pathfind::paths(map_, units_, hex, teams_,
                        false, teleport, viewing_team(), path_turns_);
            }
            show_attack_options(u);
            gui().highlight_reach(current_paths_);
            // the highlight now comes from selection
            // and not from the mouseover on an enemy
            enemy_paths_ = false;
            gui().set_route(NULL);

            // selection have impact only if we are not observing and it's our unit
            if ((!commands_disabled || resources::whiteboard->is_active()) && u->side() == gui().viewing_side()) {
                  if (!(browse || resources::whiteboard->unit_has_actions(&*u)))
                  {
                        sound::play_UI_sound("select-unit.wav");
                        u->set_selecting();
                        game_events::fire("select", hex);
                  }
            }

      } else {
            gui().unhighlight_reach();
            current_paths_ = pathfind::paths();
            current_route_.steps.clear();
            resources::whiteboard->on_deselect_hex();
      }
}

void mouse_handler::deselect_hex() {
      select_hex(map_location(), true);
}

bool mouse_handler::move_unit_along_current_route(bool check_shroud)
{
      // do not show footsteps during movement
      gui().set_route(NULL);

      // do not keep the hex highlighted that we started from
      selected_hex_ = map_location();
      gui().select_hex(map_location());

      bool finished_moves = move_unit_along_route(current_route_, &next_unit_, check_shroud);

      // invalid after the move
      current_paths_ = pathfind::paths();
      current_route_.steps.clear();

      return finished_moves;
}

bool mouse_handler::move_unit_along_route(pathfind::marked_route const& route, map_location* next_unit, bool check_shroud)
{
      const std::vector<map_location> steps = route.steps;
      if(steps.empty()) {
            return false;
      }

      size_t moves = 0;
      try {
                  moves = ::move_unit(NULL, steps, &recorder, resources::undo_stack, true, next_unit, false, check_shroud);
      } catch(end_turn_exception&) {
            cursor::set(cursor::NORMAL);
            gui().invalidate_game_status();
            throw;
      }

      cursor::set(cursor::NORMAL);
      gui().invalidate_game_status();

      if(moves == 0)
            return false;

      resources::redo_stack->clear();

      assert(moves <= steps.size());
      const map_location& dst = steps[moves-1];
      const unit_map::const_iterator u = units_.find(dst);

      //u may be equal to units_.end() in the case of e.g. a [teleport]
      if(u != units_.end()) {
            if(dst != steps.back()) {
                  // the move was interrupted (or never started)
                  if (u->movement_left() > 0) {
                        // reselect the unit (for "press t to continue")
                        select_hex(dst, false);
                        // the new discovery is more important than the new movement range
                        show_partial_move_ = true;
                        gui().unhighlight_reach();
                  }
            }
      }

      return moves == steps.size();
}

int mouse_handler::fill_weapon_choices(std::vector<battle_context>& bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
{
      int best = 0;
      for (unsigned int i = 0; i < attacker->attacks().size(); i++) {
            // skip weapons with attack_weight=0
            if (attacker->attacks()[i].attack_weight() > 0) {
                  battle_context bc(*resources::units, attacker->get_location(), defender->get_location(), i);
                  bc_vector.push_back(bc);
                  if (bc.better_attack(bc_vector[best], 0.5)) {
                        // as some weapons can be hidden, i is not a valid index into the resulting vector
                        best = bc_vector.size() - 1;
                  }
            }
      }
      return best;
}

int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
{

      unit_map::iterator attacker = units_.find(attacker_loc);
      unit_map::iterator defender = units_.find(defender_loc);
      if(attacker == units_.end() || defender == units_.end()) {
            ERR_NG << "One fighter is missing, can't attack";
            return -1; // abort, click will do nothing
      }

      std::vector<battle_context> bc_vector;
      const int best = fill_weapon_choices(bc_vector, attacker, defender);

      if(gui2::new_widgets) {
            gui2::tunit_attack dlg(
                          attacker
                        , defender
                        , bc_vector
                        , best);

            dlg.show(resources::screen->video());

            if(dlg.get_retval() == gui2::twindow::OK) {
                  return dlg.get_selected_weapon();
            } else {
                  return -1;
            }
      }

      if (bc_vector.empty())
      {
            dialogs::units_list_preview_pane attacker_preview(&*attacker, dialogs::unit_preview_pane::SHOW_BASIC, true);
            dialogs::units_list_preview_pane defender_preview(&*defender, dialogs::unit_preview_pane::SHOW_BASIC, false);
            std::vector<gui::preview_pane*> preview_panes;
            preview_panes.push_back(&attacker_preview);
            preview_panes.push_back(&defender_preview);

            gui::show_dialog(gui(), NULL, _("Attack Enemy"),
                  _("No usable weapon"), gui::CANCEL_ONLY, NULL,
                  &preview_panes, "", NULL, -1, NULL, -1, -1, NULL, NULL);
            return -1;
      }


      std::vector<std::string> items;

      for (unsigned int i = 0; i < bc_vector.size(); i++) {
            const battle_context_unit_stats& att = bc_vector[i].get_attacker_stats();
            const battle_context_unit_stats& def = bc_vector[i].get_defender_stats();
            config tmp_config;
            attack_type no_weapon(tmp_config);
            const attack_type& attw = attack_type(*att.weapon);
            const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);

            attw.set_specials_context(attacker->get_location(), defender->get_location(), *attacker, true);
            defw.set_specials_context(attacker->get_location(), defender->get_location(), *attacker, false);

            // if missing, add dummy special, to be sure to have
            // big enough mimimum width (weapon's name can be very short)
            std::string att_weapon_special = attw.weapon_specials();
            if (att_weapon_special.empty())
                  att_weapon_special += "       ";
            std::string def_weapon_special = defw.weapon_specials();
            if (def_weapon_special.empty())
                  def_weapon_special += "       ";

            std::stringstream atts;
            if (static_cast<int>(i) == best) {
                  atts << DEFAULT_ITEM;
            }

            std::string range = attw.range().empty() ? defw.range() : attw.range();
            if (!range.empty()) {
                  range = gettext(range.c_str());
            }

            // add dummy names if missing, to keep stats aligned
            std::string attw_name = attw.name();
            if(attw_name.empty())
                  attw_name = " ";
            std::string defw_name = defw.name();
            if(defw_name.empty())
                  defw_name = " ";

            // color CtH in red-yellow-green
            SDL_Color att_cth_color =
                        int_to_color( game_config::red_to_green(att.chance_to_hit) );
            SDL_Color def_cth_color =
                        int_to_color( game_config::red_to_green(def.chance_to_hit) );

            atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
                   << font::BOLD_TEXT << attw_name  << "\n"
                   << att.damage << font::weapon_numbers_sep << att.num_blows
                   << "  " << att_weapon_special << "\n"
                   << font::color2markup(att_cth_color) << att.chance_to_hit << "%"
                   << COLUMN_SEPARATOR << font::weapon_details << "- " << range << " -" << COLUMN_SEPARATOR
                   << font::BOLD_TEXT << defw_name  << "\n"
                   << def.damage << font::weapon_numbers_sep << def.num_blows
                   << "  " << def_weapon_special << "\n"
                   << font::color2markup(def_cth_color) << def.chance_to_hit << "%"
                   << COLUMN_SEPARATOR << IMAGE_PREFIX << defw.icon();

            items.push_back(atts.str());
      }

      attack_prediction_displayer ap_displayer(bc_vector, attacker_loc, defender_loc);
      std::vector<gui::dialog_button_info> buttons;
      buttons.push_back(gui::dialog_button_info(&ap_displayer, _("Damage Calculations")));

      int res = 0;
      {
            dialogs::units_list_preview_pane attacker_preview(&*attacker, dialogs::unit_preview_pane::SHOW_BASIC, true);
            dialogs::units_list_preview_pane defender_preview(&*defender, dialogs::unit_preview_pane::SHOW_BASIC, false);
            std::vector<gui::preview_pane*> preview_panes;
            preview_panes.push_back(&attacker_preview);
            preview_panes.push_back(&defender_preview);

            res = gui::show_dialog(gui(),NULL,_("Attack Enemy"),
                        _("Choose weapon:")+std::string("\n"),
                        gui::OK_CANCEL,&items,&preview_panes,"",NULL,-1,NULL,-1,-1,
                        NULL,&buttons);
      }
      cursor::set(cursor::NORMAL);

      return res;
}

void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
{
      try {
            attack_enemy_(attacker_loc, defender_loc, choice);
      } catch(std::bad_alloc) {
            lg::wml_error << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
      }
}

void mouse_handler::attack_enemy_(const map_location& att_loc
            , const map_location& def_loc
            , int choice)
{
      //NOTE: copy the values because the const reference may change!
      //(WML events and mouse inputs during animations may modify
      // the data of the caller)
      const map_location attacker_loc = att_loc;
      const map_location defender_loc = def_loc;

      //may fire event and modify things
      apply_shroud_changes(*resources::undo_stack, side_num_);
      resources::undo_stack->clear();
      resources::redo_stack->clear();

      unit_map::iterator attacker = find_unit(attacker_loc);
      if(attacker == units_.end()
                  || attacker->side() != side_num_
                  || attacker->incapacitated())
            return;

      unit_map::iterator defender = find_unit(defender_loc);
      if(defender == units_.end()
                  || current_team().is_enemy(defender->side()) == false
                  || defender->incapacitated())
            return;

      std::vector<battle_context> bc_vector;
      fill_weapon_choices(bc_vector, attacker, defender);

      if(size_t(choice) >= bc_vector.size()) {
            return;
      }

      commands_disabled++;
      const battle_context_unit_stats &att = bc_vector[choice].get_attacker_stats();
      const battle_context_unit_stats &def = bc_vector[choice].get_defender_stats();

      attacker->set_goto(map_location());

      current_paths_ = pathfind::paths();
      // make the attacker's stats appear during the attack
      gui().display_unit_hex(attacker_loc);
      // remove highlighted hexes etc..
      gui().select_hex(map_location());
      gui().highlight_hex(map_location());
      gui().clear_attack_indicator();
      gui().unhighlight_reach();
      gui().draw();

      ///@todo change ToD to be location specific for the defender
      recorder.add_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num,
            attacker->type_id(), defender->type_id(), att.level,
            def.level, resources::tod_manager->turn(), resources::tod_manager->get_time_of_day());
      rand_rng::invalidate_seed();
      if (rand_rng::has_valid_seed()) { //means SRNG is disabled
            perform_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num, rand_rng::get_last_seed());
      } else {
            rand_rng::set_new_seed_callback(boost::bind(&mouse_handler::perform_attack,
                  this, attacker_loc, defender_loc, att.attack_num, def.attack_num, _1));
      }
}

void mouse_handler::perform_attack(
      map_location attacker_loc, map_location defender_loc,
      int attacker_weapon, int defender_weapon, rand_rng::seed_t seed)
{
      // this function gets it's arguments by value because the calling function
      // object might get deleted in the clear callback call below, invalidating
      // const ref arguments
      rand_rng::clear_new_seed_callback();
      LOG_NG << "Performing attack with seed " << seed << "\n";
      recorder.add_seed("attack", seed);
      //MP_COUNTDOWN grant time bonus for attacking
      current_team().set_action_bonus_count(1 + current_team().action_bonus_count());

      try {
            events::command_disabler disabler; // Rather than decrementing for every possible exception, use RAII
            commands_disabled--;
            attack_unit(attacker_loc, defender_loc, attacker_weapon, defender_weapon);
      } catch(end_level_exception&) {
            //if the level ends due to a unit being killed, still see if
            //either the attacker or defender should advance
            dialogs::advance_unit(attacker_loc);
            unit_map::const_iterator defu = units_.find(defender_loc);
            if (defu != units_.end()) {
                  bool defender_human = teams_[defu->side() - 1].is_human();
                  dialogs::advance_unit(defender_loc, !defender_human);
            }
            throw;
      }

      dialogs::advance_unit(attacker_loc);
      unit_map::const_iterator defu = units_.find(defender_loc);
      if (defu != units_.end()) {
            bool defender_human = teams_[defu->side() - 1].is_human();
            dialogs::advance_unit(defender_loc, !defender_human);
      }

      resources::controller->check_victory();
      gui().draw();
}

std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
{
      std::set<map_location> res;

      const team& uteam = teams_[side-1];

      map_location adj[6];
      get_adjacent_tiles(loc, adj);
      foreach (const map_location &aloc, adj) {
            unit_map::const_iterator i = find_unit(aloc);
            if (i != units_.end() && uteam.is_enemy(i->side()))
                  res.insert(aloc);
      }
      return res;
}

void mouse_handler::show_attack_options(const unit_map::const_iterator &u)
{
      map_location adj[6];
      get_adjacent_tiles(u->get_location(), adj);
      foreach (const map_location &loc, adj)
      {
            if (!map_.on_board(loc)) continue;
            unit_map::const_iterator i = units_.find(loc);
            if (i == units_.end()) continue;
            const unit &target = *i;
            if (current_team().is_enemy(target.side()) && !target.incapacitated())
                  current_paths_.destinations.insert(loc);
      }
}

bool mouse_handler::unit_in_cycle(unit_map::const_iterator it)
{
      if (it == units_.end())
            return false;

      if (it->side() != side_num_ || it->user_end_turn()
          || gui().fogged(it->get_location()) || !unit_can_move(*it))
            return false;

      if (current_team().is_enemy(int(gui().viewing_team()+1)) &&
          it->invisible(it->get_location()))
            return false;

      if (it->get_hidden())
            return false;

      return true;

}

void mouse_handler::cycle_units(const bool browse, const bool reverse)
{
      if (units_.begin() == units_.end()) {
            return;
      }

      unit_map::const_iterator it = find_unit(next_unit_);
      if (it == units_.end())
            it = units_.begin();
      const unit_map::const_iterator itx = it;

      do {
            if (reverse) {
                  if (it == units_.begin())
                        it = units_.end();
                  --it;
            } else {
                  if (it == units_.end())
                        it = units_.begin();
                  else
                        ++it;
            }
      } while (it != itx && !unit_in_cycle(it));

      if (unit_in_cycle(it)) {
            gui().scroll_to_tile(it->get_location(), game_display::WARP);
            select_hex(it->get_location(), browse);
            mouse_update(browse);
      }
}

void mouse_handler::set_current_paths(pathfind::paths new_paths) {
      gui().unhighlight_reach();
      current_paths_ = new_paths;
      current_route_.steps.clear();
      gui().set_route(NULL);
      resources::whiteboard->erase_temp_move();
}

mouse_handler *mouse_handler::singleton_ = NULL;
}

Generated by  Doxygen 1.6.0   Back to index