mouse_events.cpp

Go to the documentation of this file.
00001 /* $Id: mouse_events.cpp 54160 2012-05-12 17:06:13Z jamit $ */
00002 /*
00003    Copyright (C) 2006 - 2012 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
00004    wesnoth playturn Copyright (C) 2003 by David White <dave@whitevine.net>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
00015 */
00016 
00017 #include "global.hpp"
00018 
00019 #include "mouse_events.hpp"
00020 
00021 #include "actions.hpp"
00022 #include "attack_prediction_display.hpp"
00023 #include "dialogs.hpp"
00024 #include "foreach.hpp"
00025 #include "game_end_exceptions.hpp"
00026 #include "game_events.hpp"
00027 #include "gettext.hpp"
00028 #include "gui/dialogs/unit_attack.hpp"
00029 #include "gui/widgets/settings.hpp"
00030 #include "gui/dialogs/transient_message.hpp"
00031 #include "gui/widgets/window.hpp"
00032 #include "language.hpp"
00033 #include "log.hpp"
00034 #include "map.hpp"
00035 #include "marked-up_text.hpp"
00036 #include "menu_events.hpp"
00037 #include "pathfind/teleport.hpp"
00038 #include "play_controller.hpp"
00039 #include "sound.hpp"
00040 #include "replay.hpp"
00041 #include "resources.hpp"
00042 #include "rng.hpp"
00043 #include "tod_manager.hpp"
00044 #include "wml_separators.hpp"
00045 #include "whiteboard/manager.hpp"
00046 
00047 #include <boost/bind.hpp>
00048 
00049 static lg::log_domain log_engine("engine");
00050 #define ERR_NG LOG_STREAM(err, log_engine)
00051 #define LOG_NG LOG_STREAM(info, log_engine)
00052 
00053 namespace events{
00054 
00055 
00056 mouse_handler::mouse_handler(game_display* gui, std::vector<team>& teams,
00057         unit_map& units, gamemap& map, tod_manager& tod_mng) :
00058     mouse_handler_base(),
00059     map_(map),
00060     gui_(gui),
00061     teams_(teams),
00062     units_(units),
00063     tod_manager_(tod_mng),
00064     previous_hex_(),
00065     previous_free_hex_(),
00066     selected_hex_(),
00067     next_unit_(),
00068     current_route_(),
00069     current_paths_(),
00070     enemy_paths_(false),
00071     path_turns_(0),
00072     side_num_(1),
00073     undo_(false),
00074     over_route_(false),
00075     reachmap_invalid_(false),
00076     show_partial_move_(false)
00077 {
00078     singleton_ = this;
00079 }
00080 
00081 mouse_handler::~mouse_handler()
00082 {
00083     rand_rng::clear_new_seed_callback();
00084     singleton_ = NULL;
00085 }
00086 
00087 void mouse_handler::set_side(int side_number)
00088 {
00089     side_num_ = side_number;
00090 }
00091 
00092 int mouse_handler::drag_threshold() const
00093 {
00094     return 14;
00095 }
00096 
00097 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
00098 {
00099     // we ignore the position coming from event handler
00100     // because it's always a little obsolete and we don't need
00101     // to hightlight all the hexes where the mouse passed.
00102     // Also, sometimes it seems to have one *very* obsolete
00103     // and isolated mouse motion event when using drag&drop
00104     SDL_GetMouseState(&x,&y);  // <-- modify x and y
00105 
00106     if (mouse_handler_base::mouse_motion_default(x, y, update)) return;
00107 
00108     if (new_hex == map_location::null_location)
00109         new_hex = gui().hex_clicked_on(x,y);
00110 
00111     if(new_hex != last_hex_) {
00112         update = true;
00113         if (last_hex_.valid()) {
00114             // we store the previous hexes used to propose attack direction
00115             previous_hex_ = last_hex_;
00116             // the hex of the selected unit is also "free"
00117             { // start planned unit map scope
00118                 wb::future_map_if_active raii;
00119                 if (last_hex_ == selected_hex_ || find_unit(last_hex_) == units_.end()) {
00120                     previous_free_hex_ = last_hex_;
00121                 }
00122             } // end planned unit map scope
00123         }
00124         last_hex_ = new_hex;
00125     }
00126 
00127 
00128     if (reachmap_invalid_) update = true;
00129 
00130     if (update) {
00131         if (reachmap_invalid_) {
00132             reachmap_invalid_ = false;
00133             if (!current_paths_.destinations.empty() && !show_partial_move_) {
00134                 bool selected_hex_has_unit;
00135                 { // start planned unit map scope
00136                     wb::future_map_if_active planned_unit_map;
00137                     selected_hex_has_unit = find_unit(selected_hex_) != units_.end();
00138                 } // end planned unit map scope
00139                 if(selected_hex_.valid() && selected_hex_has_unit ) {
00140                     // reselect the unit without firing events (updates current_paths_)
00141                     select_hex(selected_hex_, true);
00142                 }
00143                 // we do never deselect here, mainly because of canceled attack-move
00144             }
00145         }
00146 
00147         // reset current_route_ and current_paths if not valid anymore
00148         // we do it before cursor selection, because it uses current_paths_
00149         if(new_hex.valid() == false) {
00150             current_route_.steps.clear();
00151             gui().set_route(NULL);
00152             resources::whiteboard->erase_temp_move();
00153         }
00154 
00155         if(enemy_paths_) {
00156             enemy_paths_ = false;
00157             current_paths_ = pathfind::paths();
00158             gui().unhighlight_reach();
00159         } else if(over_route_) {
00160             over_route_ = false;
00161             current_route_.steps.clear();
00162             gui().set_route(NULL);
00163             resources::whiteboard->erase_temp_move();
00164         }
00165 
00166         gui().highlight_hex(new_hex);
00167         resources::whiteboard->on_mouseover_change(new_hex);
00168 
00169         unit_map::iterator selected_unit;
00170         unit_map::iterator mouseover_unit;
00171         map_location attack_from;
00172 
00173         { // start planned unit map scope
00174             wb::future_map_if_active planned_unit_map;
00175             selected_unit = find_unit(selected_hex_);
00176             mouseover_unit = find_unit(new_hex);
00177 
00178             // we search if there is an attack possibility and where
00179             attack_from = current_unit_attacks_from(new_hex);
00180 
00181             //see if we should show the normal cursor, the movement cursor, or
00182             //the attack cursor
00183             //If the cursor is on WAIT, we don't change it and let the setter
00184             //of this state end it
00185             if (cursor::get() != cursor::WAIT) {
00186                 if (selected_unit != units_.end() &&
00187                     selected_unit->side() == side_num_ &&
00188                     !selected_unit->incapacitated() && !browse)
00189                 {
00190                     if (attack_from.valid()) {
00191                         cursor::set(dragging_started_ ? cursor::ATTACK_DRAG : cursor::ATTACK);
00192                     }
00193                     else if (mouseover_unit==units_.end() &&
00194                              current_paths_.destinations.contains(new_hex))
00195                     {
00196                         cursor::set(dragging_started_ ? cursor::MOVE_DRAG : cursor::MOVE);
00197                     } else {
00198                         // selected unit can't attack or move there
00199                         cursor::set(cursor::NORMAL);
00200                     }
00201                 } else {
00202                     // no selected unit or we can't move it
00203                     cursor::set(cursor::NORMAL);
00204                 }
00205             }
00206         } // end planned unit map scope
00207 
00208         // show (or cancel) the attack direction indicator
00209         if (attack_from.valid() && (!browse || resources::whiteboard->is_active())) {
00210             gui().set_attack_indicator(attack_from, new_hex);
00211         } else {
00212             gui().clear_attack_indicator();
00213         }
00214 
00215         unit* un; //will later point to unit at mouseover_hex_
00216 
00217         // the destination is the pointed hex or the adjacent hex
00218         // used to attack it
00219         map_location dest;
00220         unit_map::const_iterator dest_un;
00221         { // start planned unit map scope
00222             wb::future_map_if_active raii;
00223             if (attack_from.valid()) {
00224                 dest = attack_from;
00225                 dest_un = find_unit(dest);
00226             }   else {
00227                 dest = new_hex;
00228                 dest_un = find_unit(new_hex);
00229             }
00230 
00231             if(dest == selected_hex_ || dest_un != units_.end()) {
00232                 current_route_.steps.clear();
00233                 gui().set_route(NULL);
00234                 resources::whiteboard->erase_temp_move();
00235             }
00236             else if (!current_paths_.destinations.empty() &&
00237                      map_.on_board(selected_hex_) && map_.on_board(new_hex))
00238             {
00239                 if (selected_unit != units_.end() && !selected_unit->incapacitated()) {
00240                     // Show the route from selected unit to mouseover hex
00241                     current_route_ = get_route(&*selected_unit, dest, viewing_team());
00242 
00243                     resources::whiteboard->create_temp_move();
00244 
00245                     if(!browse) {
00246                         gui().set_route(&current_route_);
00247                     }
00248                 }
00249             }
00250 
00251             unit_map::iterator iter = mouseover_unit;
00252             if (iter != units_.end())
00253                 un = &*iter;
00254             else
00255                 un = NULL;
00256         } //end planned unit map scope
00257 
00258         if (un && current_paths_.destinations.empty() &&
00259             !gui().fogged(un->get_location()))
00260         {
00261             if (un->side() != side_num_) {
00262                 //unit under cursor is not on our team, highlight reach
00263                 //Note: planned unit map must be activated after this is done,
00264                 //since the future state includes changes to units' movement.
00265                 unit_movement_resetter move_reset(*un);
00266 
00267 
00268                 { // start planned unit map scope
00269                     wb::future_map_if_active raii;
00270                     current_paths_ = pathfind::paths(map_,units_,*un,teams_,
00271                                                         false,true,viewing_team(),path_turns_);
00272                 } // end planned unit map scope
00273 
00274                 gui().highlight_reach(current_paths_);
00275                 enemy_paths_ = true;
00276             } else {
00277                 //unit is on our team, show path if the unit has one
00278                 const map_location go_to = un->get_goto();
00279                 if(map_.on_board(go_to)) {
00280                     pathfind::marked_route route;
00281                     { // start planned unit map scope
00282                         wb::future_map_if_active raii;
00283                         route = get_route(un, go_to, current_team());
00284                     } // end planned unit map scope
00285                     gui().set_route(&route);
00286                 }
00287                 over_route_ = true;
00288             }
00289         }
00290     }
00291 }
00292 
00293 unit_map::iterator mouse_handler::selected_unit()
00294 {
00295     unit_map::iterator res = find_unit(selected_hex_);
00296     if(res != units_.end()) {
00297         return res;
00298     } else {
00299         return find_unit(last_hex_);
00300     }
00301 }
00302 
00303 unit_map::iterator mouse_handler::find_unit(const map_location& hex)
00304 {
00305     unit_map::iterator it = find_visible_unit(hex, viewing_team());
00306     if (it.valid())
00307         return it;
00308     else
00309         return resources::units->end();
00310 }
00311 
00312 unit_map::const_iterator mouse_handler::find_unit(const map_location& hex) const
00313 {
00314     return find_visible_unit(hex, viewing_team());
00315 }
00316 
00317 map_location mouse_handler::current_unit_attacks_from(const map_location& loc) const
00318 {
00319     if(loc == selected_hex_)
00320         return map_location();
00321 
00322     bool wb_active = resources::whiteboard->is_active();
00323 
00324     {
00325         // Check the unit SOURCE of the attack
00326 
00327         // Check that there's a selected unit
00328         const unit_map::const_iterator source_unit = find_unit(selected_hex_);
00329         bool source_eligible = (source_unit != units_.end());
00330         if (!source_eligible) return map_location();
00331 
00332         // The selected unit must at least belong to the player currently controlling this client.
00333         source_eligible &= source_unit->side() == resources::screen->viewing_side();
00334         if (!source_eligible) return map_location();
00335 
00336         // In addition:
00337         // - If whiteboard is enabled, we allow planning attacks outside of player's turn
00338         // - If whiteboard is disabled, it must be the turn of the player controlling this client
00339         if(!wb_active) {
00340             source_eligible &= resources::screen->viewing_side() == resources::controller->current_side();
00341             if (!source_eligible) return map_location();
00342         }
00343 
00344         // Unit must have attacks left
00345         source_eligible &= source_unit->attacks_left() != 0;
00346         if (!source_eligible) return map_location();
00347 
00348 
00349         // Check the unit TARGET of the attack
00350 
00351         team const& viewing_team = (*resources::teams)[resources::screen->viewing_team()];
00352 
00353         // Check that there's a unit at the target location
00354         const unit_map::const_iterator target_unit = find_unit(loc);
00355         bool target_eligible = (target_unit != units_.end());
00356         if (!target_eligible) return map_location();
00357 
00358         // The player controlling this client must be an enemy of the target unit's side
00359         target_eligible &= viewing_team.is_enemy(target_unit->side());
00360         if (!target_eligible) return map_location();
00361 
00362         // Sanity check: source and target of the attack shouldn't be on the same team
00363         assert(source_unit->side() != target_unit->side());
00364 
00365         target_eligible &= !target_unit->incapacitated();
00366         if (!target_eligible) return map_location();
00367     }
00368 
00369     const map_location::DIRECTION preferred = loc.get_relative_dir(previous_hex_);
00370     const map_location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);
00371 
00372     int best_rating = 100;//smaller is better
00373     map_location res;
00374     map_location adj[6];
00375     get_adjacent_tiles(loc,adj);
00376 
00377     for(size_t n = 0; n != 6; ++n) {
00378         if(map_.on_board(adj[n]) == false) {
00379             continue;
00380         }
00381 
00382         if(adj[n] != selected_hex_ && find_unit(adj[n]) != units_.end()) {
00383             continue;
00384         }
00385 
00386         if (current_paths_.destinations.contains(adj[n]))
00387         {
00388             static const size_t NDIRECTIONS = map_location::NDIRECTIONS;
00389             unsigned int difference = abs(int(preferred - n));
00390             if(difference > NDIRECTIONS/2) {
00391                 difference = NDIRECTIONS - difference;
00392             }
00393             unsigned int second_difference = abs(int(second_preferred - n));
00394             if(second_difference > NDIRECTIONS/2) {
00395                 second_difference = NDIRECTIONS - second_difference;
00396             }
00397             const int rating = difference * 2 + (second_difference > difference);
00398             if(rating < best_rating || res.valid() == false) {
00399                 best_rating = rating;
00400                 res = adj[n];
00401             }
00402         }
00403     }
00404 
00405     return res;
00406 }
00407 
00408 pathfind::marked_route mouse_handler::get_route(unit* un, map_location go_to, team &team)
00409 {
00410     // The pathfinder will check unit visibility (fogged/stealthy).
00411     const pathfind::shortest_path_calculator calc(*un, team, units_, teams_, map_);
00412 
00413     pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*un, viewing_team());
00414 
00415     pathfind::plain_route route;
00416 
00417     route = pathfind::a_star_search(un->get_location(), go_to, 10000.0, &calc, map_.w(), map_.h(), &allowed_teleports);
00418 
00419     return mark_route(route);
00420 }
00421 
00422 void mouse_handler::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
00423 {
00424     mouse_handler_base::mouse_press(event, browse);
00425 }
00426 
00427 bool mouse_handler::right_click_show_menu(int x, int y, const bool browse)
00428 {
00429     // The first right-click cancel the selection if any,
00430     // the second open the context menu
00431     unit_map::iterator unit;
00432     {
00433         wb::future_map_if_active raii;
00434         unit = find_unit(selected_hex_);
00435     }
00436     if (selected_hex_.valid() && unit != units_.end()) {
00437         select_hex(map_location(), browse);
00438         return false;
00439     } else {
00440         return point_in_rect(x, y, gui().map_area());
00441     }
00442 }
00443 
00444 bool mouse_handler::left_click(int x, int y, const bool browse)
00445 {
00446     undo_ = false;
00447     if (mouse_handler_base::left_click(x, y, browse)) return false;
00448 
00449     // Lock whiteboard activation state to avoid problems due to
00450     // its changing while an animation takes place.
00451     wb::whiteboard_lock wb_lock = resources::whiteboard->get_activation_state_lock();
00452 
00453     //we use the last registered highlighted hex
00454     //since it's what update our global state
00455     map_location hex = last_hex_;
00456 
00457     unit_map::iterator u;
00458     unit_map::iterator clicked_u;
00459     map_location src;
00460     pathfind::paths orig_paths;
00461     map_location attack_from;
00462     { // start planned unit map scope
00463         wb::future_map_if_active planned_unit_map;
00464         u = find_unit(selected_hex_);
00465 
00466         //if the unit is selected and then itself clicked on,
00467         //any goto command is cancelled
00468         if (u != units_.end() && !browse && selected_hex_ == hex && u->side() == side_num_) {
00469             u->set_goto(map_location());
00470         }
00471 
00472         clicked_u = find_unit(hex);
00473 
00474         src = selected_hex_;
00475         orig_paths = current_paths_;
00476         attack_from = current_unit_attacks_from(hex);
00477     } // end planned unit map scope
00478 
00479     //see if we're trying to do a attack or move-and-attack
00480     if((!browse || resources::whiteboard->is_active()) && !commands_disabled && attack_from.valid()) {
00481 
00482         if (((u.valid() && u->side() == side_num_) || resources::whiteboard->is_active()) && clicked_u.valid() ) {
00483             if (attack_from == selected_hex_) { //no move needed
00484                 int choice = -1;
00485                 { wb::future_map_if_active planned_unit_map; //start planned unit map scope
00486                     choice = show_attack_dialog(attack_from, clicked_u->get_location());
00487                 } // end planned unit map scope
00488                 if (choice >=0 ) {
00489                     if (resources::whiteboard->is_active()) {
00490                         save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
00491                     } else {
00492                         attack_enemy(u->get_location(), clicked_u->get_location(), choice);
00493                     }
00494                 }
00495                 return false;
00496             }
00497             else {
00498 
00499                 int choice = -1; //for the attack dialog
00500 
00501                 { wb::future_map_if_active planned_unit_map; //start planned unit map scope
00502                     // we will now temporary move next to the enemy
00503                     pathfind::paths::dest_vect::const_iterator itor =
00504                             current_paths_.destinations.find(attack_from);
00505                     if(itor == current_paths_.destinations.end()) {
00506                         // can't reach the attacking location
00507                         // not supposed to happen, so abort
00508                         return false;
00509                     }
00510                     // update movement_left as if we did the move
00511                     int move_left_dst = itor->move_left;
00512                     int move_left_src = u->movement_left();
00513                     u->set_movement(move_left_dst);
00514 
00515                     // block where we temporary move the unit
00516                     {
00517                         temporary_unit_mover temp_mover(units_, src, attack_from);
00518 
00519                             choice = show_attack_dialog(attack_from, clicked_u->get_location());
00520 
00521                     }
00522                     // restore unit as before
00523                     u = units_.find(src);
00524                     u->set_movement(move_left_src);
00525                     u->set_standing();
00526 
00527                     if (choice < 0) {
00528                         // user hit cancel, don't start move+attack
00529                         return false;
00530                     }
00531                 } // end planned unit map scope
00532 
00533                 if (resources::whiteboard->is_active()) {
00534                     save_whiteboard_attack(attack_from, hex, choice);
00535                 } else {
00536                     // store side, since u may be invalidated later
00537                     int side = u->side();
00538                     //record visible enemies adjacent to destination
00539                     std::set<map_location> adj_enemies = get_adj_enemies(attack_from, side);
00540 
00541                     // move the unit without clearing fog (to avoid interruption)
00542                     //TODO: clear fog and interrupt+resume move
00543                     if(!move_unit_along_current_route(false)) {
00544                         // interrupted move
00545                         // we assume that move_unit() did the cleaning
00546                         // (update shroud/fog, clear undo if needed)
00547                         return false;
00548                     }
00549 
00550                     //check if new enemies are now visible
00551                     if(get_adj_enemies(attack_from, side) != adj_enemies)
00552                         return false; //ambush, interrupt attack
00553 
00554                     attack_enemy(attack_from, hex, choice); // Fight !!
00555                 }
00556                 return false;
00557             }
00558         }
00559     }
00560     //otherwise we're trying to move to a hex
00561     else if((!browse || resources::whiteboard->is_active()) && !commands_disabled &&
00562             selected_hex_.valid() && selected_hex_ != hex &&
00563              u != units_.end() && u.valid() &&
00564              (u->side() == side_num_ || resources::whiteboard->is_active()) &&
00565              clicked_u == units_.end() &&
00566              !current_route_.steps.empty() &&
00567              current_route_.steps.front() == selected_hex_) {
00568 
00569         // If the whiteboard is active, it intercepts any unit movement
00570         if (resources::whiteboard->is_active()) {
00571                 // Unselect the current hex, and create planned move for whiteboard
00572                 selected_hex_ = map_location();
00573                 gui().select_hex(map_location());
00574                 gui().clear_attack_indicator();
00575                 gui().set_route(NULL);
00576                 show_partial_move_ = false;
00577                 gui().unhighlight_reach();
00578                 current_paths_ = pathfind::paths();
00579                 current_route_.steps.clear();
00580 
00581                 resources::whiteboard->save_temp_move();
00582 
00583         // Otherwise proceed to normal unit movement
00584         } else {
00585             //Don't move if the selected unit already has actions
00586             //from the whiteboard.
00587             if (resources::whiteboard->unit_has_actions(&*u)) {
00588                 return false;
00589             }
00590 
00591             move_unit_along_current_route(current_team().auto_shroud_updates());
00592             // during the move, we may have selected another unit
00593             // (but without triggering a select event (command was disabled)
00594             // in that case reselect it now to fire the event (+ anim & sound)
00595             if (selected_hex_ != src) {
00596                 select_hex(selected_hex_, browse);
00597             }
00598         }
00599         return false;
00600     } else {
00601         // we select a (maybe empty) hex
00602         // we block selection during attack+move (because motion is blocked)
00603         select_hex(hex, browse);
00604     }
00605     return false;
00606     //FIXME: clean all these "return false"
00607 }
00608 
00609 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event) {
00610     selected_hex_ = hex;
00611     gui().select_hex(hex);
00612     gui().clear_attack_indicator();
00613     gui().set_route(NULL);
00614     show_partial_move_ = false;
00615 
00616     wb::future_map_if_active planned_unit_map; //lasts for whole method
00617 
00618     unit_map::iterator u = find_unit(hex);
00619 
00620     if (hex.valid() && u != units_.end() && u.valid() && !u->get_hidden()) {
00621 
00622         next_unit_ = u->get_location();
00623 
00624         {
00625             current_paths_ = pathfind::paths(map_, units_, *u, teams_,
00626                 false, true, viewing_team(), path_turns_);
00627         }
00628         if(highlight) {
00629             show_attack_options(u);
00630             gui().highlight_reach(current_paths_);
00631         }
00632         // the highlight now comes from selection
00633         // and not from the mouseover on an enemy
00634         enemy_paths_ = false;
00635         gui().set_route(NULL);
00636 
00637         // selection have impact only if we are not observing and it's our unit
00638         if ((!commands_disabled || resources::whiteboard->is_active()) && u->side() == gui().viewing_side()) {
00639             if (!(browse || resources::whiteboard->unit_has_actions(&*u)))
00640             {
00641                 sound::play_UI_sound("select-unit.wav");
00642                 u->set_selecting();
00643                 if(fire_event) {
00644                     // ensure unit map is back to normal while event is fired
00645                     wb::real_map srum;
00646                     game_events::fire("select", hex);
00647                     //end forced real unit map
00648                 }
00649             }
00650         }
00651 
00652     } else {
00653         gui().unhighlight_reach();
00654         current_paths_ = pathfind::paths();
00655         current_route_.steps.clear();
00656         resources::whiteboard->on_deselect_hex();
00657     }
00658 }
00659 
00660 void mouse_handler::deselect_hex() {
00661     select_hex(map_location(), true);
00662 }
00663 
00664 bool mouse_handler::move_unit_along_current_route(bool check_shroud)
00665 {
00666     // do not show footsteps during movement
00667     gui().set_route(NULL);
00668     gui().unhighlight_reach();
00669 
00670     // do not keep the hex highlighted that we started from
00671     selected_hex_ = map_location();
00672     gui().select_hex(map_location());
00673 
00674     bool finished_moves = move_unit_along_route(current_route_, &next_unit_, check_shroud);
00675 
00676     // invalid after the move
00677     current_paths_ = pathfind::paths();
00678     current_route_.steps.clear();
00679 
00680     return finished_moves;
00681 }
00682 
00683 bool mouse_handler::move_unit_along_route(pathfind::marked_route const& route, map_location* next_unit, bool check_shroud, bool* sighted_result)
00684 {
00685     const std::vector<map_location> steps = route.steps;
00686     if(steps.empty()) {
00687         return false;
00688     }
00689 
00690     //If this is a leader on a keep, ask permission to the whiteboard to move it
00691     //since otherwise it may cause planned recruits to be erased.
00692     {
00693         unit_map::const_iterator const u = units_.find(steps.front());
00694 
00695         if (u != units_.end()
00696                 && u->can_recruit()
00697                 && u->side() == gui().viewing_side()
00698                 && resources::game_map->is_keep(u->get_location())
00699                 && !resources::whiteboard->allow_leader_to_move(*u))
00700         {
00701             gui2::show_transient_message(gui_->video(), "",
00702                     _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
00703 
00704             if(next_unit)
00705                 *next_unit = steps.front();
00706             return false;
00707         }
00708     }
00709 
00710     size_t moves = 0;
00711     try {
00712         moves = ::move_unit(NULL, steps, &recorder, resources::undo_stack, true, next_unit, false, check_shroud, false, sighted_result);
00713     } catch(end_turn_exception&) {
00714         cursor::set(cursor::NORMAL);
00715         gui().invalidate_game_status();
00716         throw;
00717     }
00718 
00719     cursor::set(cursor::NORMAL);
00720     gui().invalidate_game_status();
00721 
00722     if(moves == 0)
00723         return false;
00724 
00725     resources::redo_stack->clear();
00726 
00727     assert(moves <= steps.size());
00728     const map_location& dst = steps[moves-1];
00729     const unit_map::const_iterator u = units_.find(dst);
00730 
00731     //u may be equal to units_.end() in the case of e.g. a [teleport]
00732     if(u != units_.end()) {
00733         if(dst != steps.back()) {
00734             // the move was interrupted (or never started)
00735             if (u->movement_left() > 0) {
00736                 // reselect the unit (for "press t to continue")
00737                 select_hex(dst, false);
00738                 // the new discovery is more important than the new movement range
00739                 show_partial_move_ = true;
00740                 gui().unhighlight_reach();
00741             }
00742         }
00743     }
00744 
00745     return moves == steps.size();
00746 }
00747 
00748 void mouse_handler::save_whiteboard_attack(const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
00749 {
00750 
00751     {
00752         // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
00753         // This method should do the cleanup of highlights and selection but it doesn't work properly
00754 
00755         // gui().highlight_hex(map_location());
00756 
00757         gui().draw();
00758         gui().unhighlight_reach();
00759         gui().clear_attack_indicator();
00760 
00761         // remove footsteps if any - useless for whiteboard as of now
00762         gui().set_route(NULL);
00763 
00764         // do not keep the hex that we started from highlighted
00765         selected_hex_ = map_location();
00766         gui().select_hex(map_location());
00767         show_partial_move_ = false;
00768 
00769         // invalid after saving the move
00770         current_paths_ = pathfind::paths();
00771         current_route_.steps.clear();
00772     }
00773 
00774     //create planned attack for whiteboard
00775     resources::whiteboard->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
00776 
00777 }
00778 
00779 int mouse_handler::fill_weapon_choices(std::vector<battle_context>& bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
00780 {
00781     int best = 0;
00782     for (unsigned int i = 0; i < attacker->attacks().size(); i++) {
00783         // skip weapons with attack_weight=0
00784         if (attacker->attacks()[i].attack_weight() > 0) {
00785             battle_context bc(*resources::units, attacker->get_location(), defender->get_location(), i);
00786             bc_vector.push_back(bc);
00787             if (bc.better_attack(bc_vector[best], 0.5)) {
00788                 // as some weapons can be hidden, i is not a valid index into the resulting vector
00789                 best = bc_vector.size() - 1;
00790             }
00791         }
00792     }
00793     return best;
00794 }
00795 
00796 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
00797 {
00798 
00799     unit_map::iterator attacker = units_.find(attacker_loc);
00800     unit_map::iterator defender = units_.find(defender_loc);
00801     if(attacker == units_.end() || defender == units_.end()) {
00802         ERR_NG << "One fighter is missing, can't attack";
00803         return -1; // abort, click will do nothing
00804     }
00805 
00806     std::vector<battle_context> bc_vector;
00807     const int best = fill_weapon_choices(bc_vector, attacker, defender);
00808 
00809     if(gui2::new_widgets) {
00810         gui2::tunit_attack dlg(
00811                   attacker
00812                 , defender
00813                 , bc_vector
00814                 , best);
00815 
00816         dlg.show(resources::screen->video());
00817 
00818         if(dlg.get_retval() == gui2::twindow::OK) {
00819             return dlg.get_selected_weapon();
00820         } else {
00821             return -1;
00822         }
00823     }
00824 
00825     if (bc_vector.empty())
00826     {
00827         dialogs::units_list_preview_pane attacker_preview(&*attacker, dialogs::unit_preview_pane::SHOW_BASIC, true);
00828         dialogs::units_list_preview_pane defender_preview(&*defender, dialogs::unit_preview_pane::SHOW_BASIC, false);
00829         std::vector<gui::preview_pane*> preview_panes;
00830         preview_panes.push_back(&attacker_preview);
00831         preview_panes.push_back(&defender_preview);
00832 
00833         gui::show_dialog(gui(), NULL, _("Attack Enemy"),
00834             _("No usable weapon"), gui::CANCEL_ONLY, NULL,
00835             &preview_panes, "", NULL, -1, NULL, -1, -1, NULL, NULL);
00836         return -1;
00837     }
00838 
00839 
00840     std::vector<std::string> items;
00841 
00842     for (unsigned int i = 0; i < bc_vector.size(); i++) {
00843         const battle_context_unit_stats& att = bc_vector[i].get_attacker_stats();
00844         const battle_context_unit_stats& def = bc_vector[i].get_defender_stats();
00845         config tmp_config;
00846         attack_type no_weapon(tmp_config);
00847         const attack_type& attw = attack_type(*att.weapon);
00848         const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);
00849 
00850         attw.set_specials_context(attacker->get_location(), defender->get_location(), *attacker, true);
00851         defw.set_specials_context(attacker->get_location(), defender->get_location(), *attacker, false);
00852 
00853         // if missing, add dummy special, to be sure to have
00854         // big enough minimum width (weapon's name can be very short)
00855         std::string att_weapon_special = attw.weapon_specials();
00856         if (att_weapon_special.empty())
00857             att_weapon_special += "       ";
00858         std::string def_weapon_special = defw.weapon_specials();
00859         if (def_weapon_special.empty())
00860             def_weapon_special += "       ";
00861 
00862         std::stringstream atts;
00863         if (static_cast<int>(i) == best) {
00864             atts << DEFAULT_ITEM;
00865         }
00866 
00867         std::string range = attw.range().empty() ? defw.range() : attw.range();
00868         if (!range.empty()) {
00869             range = string_table["range_" + range];
00870         }
00871 
00872         // add dummy names if missing, to keep stats aligned
00873         std::string attw_name = attw.name();
00874         if(attw_name.empty())
00875             attw_name = " ";
00876         std::string defw_name = defw.name();
00877         if(defw_name.empty())
00878             defw_name = " ";
00879 
00880         // color CtH in red-yellow-green
00881         SDL_Color att_cth_color =
00882                 int_to_color( game_config::red_to_green(att.chance_to_hit) );
00883         SDL_Color def_cth_color =
00884                 int_to_color( game_config::red_to_green(def.chance_to_hit) );
00885 
00886         atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
00887              << font::BOLD_TEXT << attw_name  << "\n"
00888              << att.damage << font::weapon_numbers_sep << att.num_blows
00889              << "  " << att_weapon_special << "\n"
00890              << font::color2markup(att_cth_color) << att.chance_to_hit << "%"
00891              << COLUMN_SEPARATOR << font::weapon_details << utils::unicode_em_dash + " " << range << " " + utils::unicode_em_dash << COLUMN_SEPARATOR
00892              << font::BOLD_TEXT << defw_name  << "\n"
00893              << def.damage << font::weapon_numbers_sep << def.num_blows
00894              << "  " << def_weapon_special << "\n"
00895              << font::color2markup(def_cth_color) << def.chance_to_hit << "%"
00896              << COLUMN_SEPARATOR << IMAGE_PREFIX << defw.icon();
00897 
00898         items.push_back(atts.str());
00899     }
00900 
00901     attack_prediction_displayer ap_displayer(bc_vector, attacker_loc, defender_loc);
00902     std::vector<gui::dialog_button_info> buttons;
00903     buttons.push_back(gui::dialog_button_info(&ap_displayer, _("Damage Calculations")));
00904 
00905     int res = 0;
00906     {
00907         dialogs::units_list_preview_pane attacker_preview(&*attacker, dialogs::unit_preview_pane::SHOW_BASIC, true);
00908         dialogs::units_list_preview_pane defender_preview(&*defender, dialogs::unit_preview_pane::SHOW_BASIC, false);
00909         std::vector<gui::preview_pane*> preview_panes;
00910         preview_panes.push_back(&attacker_preview);
00911         preview_panes.push_back(&defender_preview);
00912 
00913         res = gui::show_dialog(gui(),NULL,_("Attack Enemy"),
00914                 _("Choose weapon:")+std::string("\n"),
00915                 gui::OK_CANCEL,&items,&preview_panes,"",NULL,-1,NULL,-1,-1,
00916                 NULL,&buttons);
00917     }
00918     cursor::set(cursor::NORMAL);
00919 
00920     return res;
00921 }
00922 
00923 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
00924 {
00925     try {
00926         attack_enemy_(attacker_loc, defender_loc, choice);
00927     } catch(std::bad_alloc) {
00928         lg::wml_error << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
00929     }
00930 }
00931 
00932 void mouse_handler::attack_enemy_(const map_location& att_loc
00933         , const map_location& def_loc
00934         , int choice)
00935 {
00936     //NOTE: copy the values because the const reference may change!
00937     //(WML events and mouse inputs during animations may modify
00938     // the data of the caller)
00939     const map_location attacker_loc = att_loc;
00940     const map_location defender_loc = def_loc;
00941 
00942     //may fire event and modify things
00943     apply_shroud_changes(*resources::undo_stack, side_num_);
00944     resources::undo_stack->clear();
00945     resources::redo_stack->clear();
00946 
00947     unit_map::iterator attacker = find_unit(attacker_loc);
00948     if(attacker == units_.end()
00949             || attacker->side() != side_num_
00950             || attacker->incapacitated())
00951         return;
00952 
00953     unit_map::iterator defender = find_unit(defender_loc);
00954     if(defender == units_.end()
00955             || current_team().is_enemy(defender->side()) == false
00956             || defender->incapacitated())
00957         return;
00958 
00959     std::vector<battle_context> bc_vector;
00960     fill_weapon_choices(bc_vector, attacker, defender);
00961 
00962     if(size_t(choice) >= bc_vector.size()) {
00963         return;
00964     }
00965 
00966     commands_disabled++;
00967     const battle_context_unit_stats &att = bc_vector[choice].get_attacker_stats();
00968     const battle_context_unit_stats &def = bc_vector[choice].get_defender_stats();
00969 
00970     attacker->set_goto(map_location());
00971 
00972     current_paths_ = pathfind::paths();
00973     // make the attacker's stats appear during the attack
00974     gui().display_unit_hex(attacker_loc);
00975     // remove highlighted hexes etc..
00976     gui().select_hex(map_location());
00977     gui().highlight_hex(map_location());
00978     gui().clear_attack_indicator();
00979     gui().unhighlight_reach();
00980     gui().draw();
00981 
00982     ///@todo change ToD to be location specific for the defender
00983     recorder.add_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num,
00984         attacker->type_id(), defender->type_id(), att.level,
00985         def.level, resources::tod_manager->turn(), resources::tod_manager->get_time_of_day());
00986     rand_rng::invalidate_seed();
00987     if (rand_rng::has_valid_seed()) { //means SRNG is disabled
00988         perform_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num, rand_rng::get_last_seed());
00989     } else {
00990         rand_rng::set_new_seed_callback(boost::bind(&mouse_handler::perform_attack,
00991             this, attacker_loc, defender_loc, att.attack_num, def.attack_num, _1));
00992     }
00993 }
00994 
00995 void mouse_handler::perform_attack(
00996     map_location attacker_loc, map_location defender_loc,
00997     int attacker_weapon, int defender_weapon, int seed)
00998 {
00999     // this function gets it's arguments by value because the calling function
01000     // object might get deleted in the clear callback call below, invalidating
01001     // const ref arguments
01002     rand_rng::clear_new_seed_callback();
01003     LOG_NG << "Performing attack with seed " << seed << "\n";
01004     recorder.add_seed("attack", seed);
01005     //MP_COUNTDOWN grant time bonus for attacking
01006     current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
01007 
01008     try {
01009         events::command_disabler disabler; // Rather than decrementing for every possible exception, use RAII
01010         commands_disabled--;
01011         attack_unit(attacker_loc, defender_loc, attacker_weapon, defender_weapon);
01012     } catch(end_level_exception&) {
01013         //if the level ends due to a unit being killed, still see if
01014         //either the attacker or defender should advance
01015         dialogs::advance_unit(attacker_loc);
01016         unit_map::const_iterator defu = units_.find(defender_loc);
01017         if (defu != units_.end()) {
01018             bool defender_human = teams_[defu->side() - 1].is_human();
01019             dialogs::advance_unit(defender_loc, !defender_human);
01020         }
01021         throw;
01022     }
01023 
01024     dialogs::advance_unit(attacker_loc);
01025     unit_map::const_iterator defu = units_.find(defender_loc);
01026     if (defu != units_.end()) {
01027         bool defender_human = teams_[defu->side() - 1].is_human();
01028         dialogs::advance_unit(defender_loc, !defender_human);
01029     }
01030 
01031     resources::controller->check_victory();
01032     gui().draw();
01033 }
01034 
01035 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
01036 {
01037     std::set<map_location> res;
01038 
01039     const team& uteam = teams_[side-1];
01040 
01041     map_location adj[6];
01042     get_adjacent_tiles(loc, adj);
01043     foreach (const map_location &aloc, adj) {
01044         unit_map::const_iterator i = find_unit(aloc);
01045         if (i != units_.end() && uteam.is_enemy(i->side()))
01046             res.insert(aloc);
01047     }
01048     return res;
01049 }
01050 
01051 /**
01052  * Causes attackable hexes to be higlighted.
01053  *
01054  * This checks the hexes that the provided unit can attack. If there is a valid
01055  * target there, that location is inserted into current_paths_.destinations.
01056  */
01057 void mouse_handler::show_attack_options(const unit_map::const_iterator &u)
01058 {
01059     // Cannot attack if no attacks are left.
01060     if (u->attacks_left() == 0)
01061           return;
01062 
01063     // Get the teams involved.
01064     const team & cur_team = current_team();
01065     const team & u_team = teams_[u->side()-1];
01066 
01067     // Check each adjacent hex.
01068     map_location adj[6];
01069     get_adjacent_tiles(u->get_location(), adj);
01070     foreach (const map_location &loc, adj)
01071     {
01072         // No attack option shown if no visible unit present.
01073         // (Visible to current team, not necessarily the unit's team.)
01074         if (!map_.on_board(loc)) continue;
01075         unit_map::const_iterator i = units_.find(loc);
01076         if ( i == units_.end()  ||  !i->is_visible_to_team(cur_team) )
01077             continue;
01078         const unit &target = *i;
01079         // Can only attack non-petrified enemies.
01080         if ( u_team.is_enemy(target.side())  &&  !target.incapacitated() )
01081             current_paths_.destinations.insert(loc);
01082     }
01083 }
01084 
01085 bool mouse_handler::unit_in_cycle(unit_map::const_iterator it)
01086 {
01087     if (it == units_.end())
01088         return false;
01089 
01090     if (it->side() != side_num_ || it->user_end_turn()
01091         || gui().fogged(it->get_location()) || !unit_can_move(*it))
01092         return false;
01093 
01094     if (current_team().is_enemy(int(gui().viewing_team()+1)) &&
01095         it->invisible(it->get_location()))
01096         return false;
01097 
01098     if (it->get_hidden())
01099         return false;
01100 
01101     return true;
01102 
01103 }
01104 
01105 void mouse_handler::cycle_units(const bool browse, const bool reverse)
01106 {
01107     if (units_.begin() == units_.end()) {
01108         return;
01109     }
01110 
01111     unit_map::const_iterator it = find_unit(next_unit_);
01112     if (it == units_.end())
01113         it = units_.begin();
01114     const unit_map::const_iterator itx = it;
01115 
01116     do {
01117         if (reverse) {
01118             if (it == units_.begin())
01119                 it = units_.end();
01120             --it;
01121         } else {
01122             if (it == units_.end())
01123                 it = units_.begin();
01124             else
01125                 ++it;
01126         }
01127     } while (it != itx && !unit_in_cycle(it));
01128 
01129     if (unit_in_cycle(it)) {
01130         gui().scroll_to_tile(it->get_location(), game_display::WARP);
01131         select_hex(it->get_location(), browse);
01132 //      mouse_update(browse);
01133     }
01134 }
01135 
01136 void mouse_handler::set_current_paths(pathfind::paths new_paths) {
01137     gui().unhighlight_reach();
01138     current_paths_ = new_paths;
01139     current_route_.steps.clear();
01140     gui().set_route(NULL);
01141     resources::whiteboard->erase_temp_move();
01142 }
01143 
01144 mouse_handler *mouse_handler::singleton_ = NULL;
01145 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:05 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs