mouse_events.cpp

Go to the documentation of this file.
00001 /* $Id: mouse_events.cpp 26460 2008-05-08 18:15:56Z mordante $ */
00002 /*
00003    Copyright (C) 2006 - 2008 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 version 2
00009    or at your option any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 #include "mouse_events.hpp"
00017 
00018 #include "attack_prediction.hpp"
00019 #include "cursor.hpp"
00020 #include "dialogs.hpp"
00021 #include "game_events.hpp"
00022 #include "gettext.hpp"
00023 #include "log.hpp"
00024 #include "marked-up_text.hpp"
00025 #include "menu_events.hpp"
00026 #include "preferences_display.hpp"
00027 #include "sound.hpp"
00028 #include "replay.hpp"
00029 #include "show_dialog.hpp"
00030 #include "unit_abilities.hpp"
00031 #include "wml_separators.hpp"
00032 #include "unit_display.hpp"
00033 #include "sdl_utils.hpp"
00034 
00035 #include <cassert>
00036 #include <cstdlib>
00037 
00038 namespace events{
00039 
00040 int commands_disabled = 0;
00041 
00042 command_disabler::command_disabler()
00043 {
00044     ++commands_disabled;
00045 }
00046 
00047 command_disabler::~command_disabler()
00048 {
00049     --commands_disabled;
00050 }
00051 
00052 static bool command_active()
00053 {
00054 #ifdef __APPLE__
00055     return (SDL_GetModState()&KMOD_META) != 0;
00056 #else
00057     return false;
00058 #endif
00059 }
00060 
00061 // Conversion routine for both unscatched and damage change percentage.
00062 static void format_prob(char str_buf[10], const float prob)
00063 {
00064 
00065     if(prob > 0.9995) {
00066         snprintf(str_buf, 10, "100 %%");
00067     } else if(prob >= 0.1) { 
00068         snprintf(str_buf, 10, "%4.1f %%", 
00069             static_cast<float>(100.0 * (prob + 0.0005)));
00070     } else { 
00071         snprintf(str_buf, 10, " %3.1f %%", 
00072             static_cast<float>(100.0 * (prob + 0.0005)));
00073     }
00074 
00075     str_buf[9] = '\0';  //prevents _snprintf error
00076 }
00077 
00078 namespace{
00079     //minimum dragging distance to fire the drag&drop
00080     const double drag_threshold = 14.0;
00081 
00082     // This preview pane is shown in the "Damage Calculations" dialog.
00083     class battle_prediction_pane : public gui::preview_pane
00084     {
00085     public:
00086 
00087         // Lengthy constructor.
00088         battle_prediction_pane(game_display &disp, const battle_context& bc, const gamemap& map,
00089                                const std::vector<team>& teams, const unit_map& units,
00090                                const gamestatus& status,
00091                                const gamemap::location& attacker_loc, const gamemap::location& defender_loc);
00092 
00093         // This method is called to draw the dialog contents.
00094         void draw_contents();
00095 
00096         // Hack: pretend the preview pane goes to the left.
00097         bool left_side() const { return 1; }
00098 
00099         // Unused.
00100         void set_selection(int) {}
00101 
00102     private:
00103         game_display &disp_;
00104         const battle_context& bc_;
00105         const gamemap& map_;
00106         const std::vector<team>& teams_;
00107         const unit_map& units_;
00108         const gamestatus& status_;
00109         const gamemap::location& attacker_loc_;
00110         const gamemap::location& defender_loc_;
00111         const unit& attacker_;
00112         const unit& defender_;
00113 
00114         // Layout constants.
00115         static const int inter_line_gap_;
00116         static const int inter_column_gap_;
00117         static const int inter_units_gap_;
00118         static const int max_hp_distrib_rows_;
00119 
00120         // Layout computations.
00121         std::string attacker_label_, defender_label_;
00122         int attacker_label_width_, defender_label_width_;
00123 
00124         std::vector<std::string> attacker_left_strings_, attacker_right_strings_;
00125         std::vector<std::string> defender_left_strings_, defender_right_strings_;
00126         int attacker_strings_width_, attacker_left_strings_width_, attacker_right_strings_width_;
00127         int defender_strings_width_, defender_left_strings_width_, defender_right_strings_width_;
00128         int units_strings_height_;
00129 
00130         std::string hp_distrib_string_;
00131         surface attacker_hp_distrib_, defender_hp_distrib_;
00132         int hp_distrib_string_width_;
00133         int attacker_hp_distrib_width_, defender_hp_distrib_width_;
00134         int attacker_hp_distrib_height_, defender_hp_distrib_height_, hp_distribs_height_;
00135 
00136         int attacker_width_, defender_width_, units_width_;
00137         int dialog_width_, dialog_height_;
00138 
00139         // This method builds the strings describing the unit damage modifiers.
00140         // Read the code to understand the arguments.
00141         void get_unit_strings(const battle_context::unit_stats& stats,
00142                           const unit& u, const gamemap::location& u_loc, float u_unscathed,
00143                           const unit& opp, const gamemap::location& opp_loc, const attack_type *opp_weapon,
00144                           std::vector<std::string>& left_strings, std::vector<std::string>& right_strings,
00145                           int& left_strings_width, int& right_strings_width, int& strings_width);
00146 
00147         // Utility method that returns the length of the longest string in a vector of strings.
00148         int get_strings_max_length(const std::vector<std::string>& strings);
00149 
00150         // This method builds the vector containing the <HP, probability> pairs
00151         // that are required to draw the image of the hitpoints distribution of
00152         // a combatant after a fight. The method takes as input the probability
00153         // distribution of the hitpoints of the combatant after the fight.
00154         void get_hp_prob_vector(const std::vector<double>& hp_dist,
00155                                 std::vector<std::pair<int, double> >& hp_prob_vector);
00156 
00157         // This method draws a unit in the dialog pane. Read the code to understand
00158         // the arguments.
00159         void draw_unit(int x_off, int damage_line_skip, int left_strings_width,
00160                        const std::vector<std::string>& left_strings,
00161                        const std::vector<std::string>& right_strings,
00162                        const std::string& label, int label_width,
00163                        surface& hp_distrib, int hp_distrib_width);
00164 
00165         // This method draws the image of the hitpoints distribution of a
00166         // combatant after a fight. The method takes as input the
00167         // "hp_prob_vector" computed above and the stats of the combatants.
00168         // It draws the image in the surface 'surf' and set the width and
00169         // height of the image in the fields specified.
00170         void get_hp_distrib_surface(const std::vector<std::pair<int, double> >& hp_prob_vector,
00171                                 const battle_context::unit_stats& stats,
00172                                     const battle_context::unit_stats& opp_stats,
00173                                     surface& surf, int& width, int& height);
00174 
00175         // This method blends a RGB color. The method takes as input a surface,
00176         // the RGB color to blend and a value specifying how much blending to
00177         // apply. The blended color is returned. Caution: if you use a
00178         // transparent color, make sure the resulting color is not equal to the
00179         // transparent color.
00180         Uint32 blend_rgb(const surface& surf, unsigned char r, unsigned char g, unsigned char b, unsigned char drop);
00181     };
00182 
00183     const int battle_prediction_pane::inter_line_gap_ = 3;
00184     const int battle_prediction_pane::inter_column_gap_ = 30;
00185     const int battle_prediction_pane::inter_units_gap_ = 30;
00186     const int battle_prediction_pane::max_hp_distrib_rows_ = 10;
00187 
00188     battle_prediction_pane::battle_prediction_pane(game_display &disp, const battle_context& bc, const gamemap& map,
00189                                                    const std::vector<team>& teams, const unit_map& units,
00190                                                    const gamestatus& status,
00191                                                    const gamemap::location& attacker_loc, const gamemap::location& defender_loc)
00192                 : gui::preview_pane(disp.video()), disp_(disp), bc_(bc), map_(map), teams_(teams), units_(units), status_(status),
00193                   attacker_loc_(attacker_loc), defender_loc_(defender_loc),
00194                   attacker_(units.find(attacker_loc)->second), defender_(units.find(defender_loc)->second)
00195     {
00196         // Predict the battle outcome.
00197         combatant attacker_combatant(bc.get_attacker_stats());
00198         combatant defender_combatant(bc.get_defender_stats());
00199         attacker_combatant.fight(defender_combatant);
00200 
00201         const battle_context::unit_stats& attacker_stats = bc.get_attacker_stats();
00202         const battle_context::unit_stats& defender_stats = bc.get_defender_stats();
00203 
00204         // Create the hitpoints distribution graphics.
00205         std::vector<std::pair<int, double> > hp_prob_vector;
00206         get_hp_prob_vector(attacker_combatant.hp_dist, hp_prob_vector);
00207         get_hp_distrib_surface(hp_prob_vector, attacker_stats, defender_stats, attacker_hp_distrib_,
00208                                attacker_hp_distrib_width_, attacker_hp_distrib_height_);
00209         get_hp_prob_vector(defender_combatant.hp_dist, hp_prob_vector);
00210         get_hp_distrib_surface(hp_prob_vector, defender_stats, attacker_stats, defender_hp_distrib_,
00211                            defender_hp_distrib_width_, defender_hp_distrib_height_);
00212         hp_distribs_height_ = maximum<int>(attacker_hp_distrib_height_, defender_hp_distrib_height_);
00213 
00214         // Build the strings and compute the layout.
00215         std::stringstream str;
00216 
00217         attacker_label_ = _("Attacker");
00218         defender_label_ = _("Defender");
00219         attacker_label_width_ = font::line_width(attacker_label_, font::SIZE_PLUS, TTF_STYLE_BOLD);
00220         defender_label_width_ = font::line_width(defender_label_, font::SIZE_PLUS, TTF_STYLE_BOLD);
00221 
00222         // Get the units strings.
00223         get_unit_strings(attacker_stats, attacker_, attacker_loc_, attacker_combatant.untouched,
00224                          defender_, defender_loc_, defender_stats.weapon,
00225                          attacker_left_strings_, attacker_right_strings_,
00226                          attacker_left_strings_width_, attacker_right_strings_width_, attacker_strings_width_);
00227 
00228         get_unit_strings(defender_stats, defender_, defender_loc_, defender_combatant.untouched,
00229                          attacker_, attacker_loc_, attacker_stats.weapon,
00230                          defender_left_strings_, defender_right_strings_,
00231                          defender_left_strings_width_, defender_right_strings_width_, defender_strings_width_);
00232 
00233         units_strings_height_ = maximum<int>(attacker_left_strings_.size(), defender_left_strings_.size())
00234                                 * (font::SIZE_NORMAL + inter_line_gap_) + 14;
00235 
00236         hp_distrib_string_ = _("Expected Battle Result (HP)");
00237         hp_distrib_string_width_ = font::line_width(hp_distrib_string_, font::SIZE_SMALL);
00238 
00239         attacker_width_ = maximum<int>(attacker_label_width_, attacker_strings_width_);
00240         attacker_width_ = maximum<int>(attacker_width_, hp_distrib_string_width_);
00241         attacker_width_ = maximum<int>(attacker_width_, attacker_hp_distrib_width_);
00242         defender_width_ = maximum<int>(defender_label_width_, defender_strings_width_);
00243         defender_width_ = maximum<int>(defender_width_, hp_distrib_string_width_);
00244         defender_width_ = maximum<int>(defender_width_, defender_hp_distrib_width_);
00245         units_width_ = maximum<int>(attacker_width_, defender_width_);
00246 
00247         dialog_width_ = 2 * units_width_ + inter_units_gap_;
00248         dialog_height_ = 15 + 24 + units_strings_height_ + 14 + 19 + hp_distribs_height_ + 18;
00249 
00250         // Set the dialog size.
00251         set_measurements(dialog_width_, dialog_height_);
00252     }
00253 
00254     void battle_prediction_pane::get_unit_strings(const battle_context::unit_stats& stats,
00255                                               const unit& u, const gamemap::location& u_loc, float u_unscathed,
00256                                               const unit& opp, const gamemap::location& opp_loc, const attack_type *opp_weapon,
00257                                                   std::vector<std::string>& left_strings, std::vector<std::string>& right_strings,
00258                                               int& left_strings_width, int& right_strings_width, int& strings_width)
00259     {
00260         std::stringstream str;
00261         char str_buf[10];
00262 
00263         // With a weapon.
00264         if(stats.weapon != NULL) {
00265 
00266             // Set specials context (for safety, it should not have changed normally).
00267             const attack_type *weapon = stats.weapon;
00268             weapon->set_specials_context(u_loc, opp_loc, &units_, &map_, &status_, &teams_, stats.is_attacker, opp_weapon);
00269 
00270             // Get damage modifiers.
00271             unit_ability_list dmg_specials = weapon->get_specials("damage");
00272             unit_abilities::effect dmg_effect(dmg_specials, weapon->damage(), stats.backstab_pos);
00273 
00274             // Get the SET damage modifier, if any.
00275             const unit_abilities::individual_effect *set_dmg_effect = NULL;
00276             unit_abilities::effect_list::const_iterator i;
00277             for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
00278                 if(i->type == unit_abilities::SET) {
00279                     set_dmg_effect = &*i;
00280                     break;
00281                 }
00282             }
00283 
00284             // Either user the SET modifier or the base weapon damage.
00285             if(set_dmg_effect == NULL) {
00286                 left_strings.push_back(weapon->name());
00287                 str.str("");
00288                 str << weapon->damage();
00289                 right_strings.push_back(str.str());
00290             } else {
00291                 left_strings.push_back((*set_dmg_effect->ability)["name"]);
00292                 str.str("");
00293                 str << set_dmg_effect->value;
00294                 right_strings.push_back(str.str());
00295             }
00296 
00297             // Process the ADD damage modifiers.
00298             for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
00299                 if(i->type == unit_abilities::ADD) {
00300                     left_strings.push_back((*i->ability)["name"]);
00301                     str.str("");
00302                     if(i->value >= 0) str << "+" << i->value;
00303                     else str << i->value;
00304                     right_strings.push_back(str.str());
00305                 }
00306             }
00307 
00308             // Process the MUL damage modifiers.
00309             for(i = dmg_effect.begin(); i != dmg_effect.end(); ++i) {
00310                 if(i->type == unit_abilities::MUL) {
00311                     left_strings.push_back((*i->ability)["name"]);
00312                     str.str("");
00313                     str << "* " << (i->value / 100);
00314                     if(i->value % 100) {
00315                         str << "." << ((i->value % 100) / 10);
00316                         if(i->value % 10) str << (i->value % 10);
00317                     }
00318                     right_strings.push_back(str.str());
00319                 }
00320             }
00321 
00322             // Resistance modifier.
00323             int resistance_modifier = opp.damage_from(*weapon, !stats.is_attacker, opp_loc);
00324             if(resistance_modifier != 100) {
00325                 str.str("");
00326                 if(stats.is_attacker) str << _("Defender");
00327                 else str << _("Attacker");
00328                 if(resistance_modifier < 100) str << _(" resistance vs ");
00329                 else str << _(" vulnerability vs ");
00330                 str << gettext(weapon->type().c_str());
00331                 left_strings.push_back(str.str());
00332                 str.str("");
00333                 str << "* " << (resistance_modifier / 100) << "." << ((resistance_modifier % 100) / 10);
00334                 right_strings.push_back(str.str());
00335             }
00336 
00337             // Slowed penalty.
00338             if(stats.is_slowed) {
00339                 left_strings.push_back(_("Slowed"));
00340                 right_strings.push_back("* 0.5");
00341             }
00342 
00343             // Time of day modifier.
00344             int tod_modifier = combat_modifier(status_, units_, u_loc, u.alignment(), u.is_fearless(), map_);
00345             if(tod_modifier != 0) {
00346                 left_strings.push_back(_("Time of day"));
00347                 str.str("");
00348                 str << (tod_modifier > 0 ? "+" : "") << tod_modifier << "%";
00349                 right_strings.push_back(str.str());
00350             }
00351 
00352     // Leadership bonus.
00353     int leadership_bonus = 0;
00354     under_leadership(units_, u_loc, &leadership_bonus);
00355             if(leadership_bonus != 0) {
00356                 left_strings.push_back(_("Leadership"));
00357                 str.str("");
00358                 str << "+" << leadership_bonus << "%";
00359                 right_strings.push_back(str.str());
00360             }
00361 
00362             // Total damage.
00363             left_strings.push_back(_("Total damage"));
00364             str.str("");
00365             str << stats.damage << "-" << stats.num_blows << " (" << stats.chance_to_hit << "%)";
00366             right_strings.push_back(str.str());
00367 
00368         // Without a weapon.
00369         } else {
00370             left_strings.push_back(_("No usable weapon"));
00371             right_strings.push_back("");
00372         }
00373 
00374         // Unscathed probability.
00375         left_strings.push_back(_("Chance of being unscathed"));
00376         format_prob(str_buf, u_unscathed);
00377         right_strings.push_back(str_buf);
00378 
00379 #if 0 // might not be en English!
00380         // Fix capitalisation of left strings.
00381         for(int i = 0; i < (int) left_strings.size(); i++)
00382             if(left_strings[i].size() > 0) left_strings[i][0] = toupper(left_strings[i][0]);
00383 #endif
00384 
00385         // Compute the width of the strings.
00386         left_strings_width = get_strings_max_length(left_strings);
00387         right_strings_width = get_strings_max_length(right_strings);
00388         strings_width = left_strings_width + inter_column_gap_ + right_strings_width;
00389     }
00390 
00391     int battle_prediction_pane::get_strings_max_length(const std::vector<std::string>& strings)
00392     {
00393         int max_len = 0;
00394 
00395         for(int i = 0; i < static_cast<int>(strings.size()); i++)
00396             max_len = maximum<int>(font::line_width(strings[i], font::SIZE_NORMAL), max_len);
00397 
00398         return max_len;
00399     }
00400 
00401     void battle_prediction_pane::get_hp_prob_vector(const std::vector<double>& hp_dist,
00402                                                     std::vector<std::pair<int, double> >& hp_prob_vector)
00403     {
00404         hp_prob_vector.clear();
00405 
00406         // First, we sort the probabilities in ascending order.
00407         std::vector<std::pair<double, int> > prob_hp_vector;
00408         int i;
00409 
00410         for(i = 0; i < static_cast<int>(hp_dist.size()); i++) {
00411             double prob = hp_dist[i];
00412 
00413             // We keep only values above 0.1%.
00414             if(prob > 0.001)
00415                 prob_hp_vector.push_back(std::pair<double, int>(prob, i));
00416         }
00417 
00418         std::sort(prob_hp_vector.begin(), prob_hp_vector.end());
00419 
00420         // We store a few of the highest probability hitpoint values.
00421         int nb_elem = minimum<int>(max_hp_distrib_rows_, prob_hp_vector.size());
00422 
00423         for(i = prob_hp_vector.size() - nb_elem;
00424                 i < static_cast<int>(prob_hp_vector.size()); i++) {
00425 
00426             hp_prob_vector.push_back(std::pair<int, double>
00427                 (prob_hp_vector[i].second, prob_hp_vector[i].first));
00428             }
00429 
00430         // Then, we sort the hitpoint values in ascending order.
00431         std::sort(hp_prob_vector.begin(), hp_prob_vector.end());
00432     }
00433 
00434     void battle_prediction_pane::draw_contents()
00435     {
00436         // We must align both damage lines.
00437         int damage_line_skip = maximum<int>(attacker_left_strings_.size(), defender_left_strings_.size()) - 2;
00438 
00439         draw_unit(0, damage_line_skip,
00440                   attacker_left_strings_width_, attacker_left_strings_, attacker_right_strings_,
00441                   attacker_label_, attacker_label_width_, attacker_hp_distrib_, attacker_hp_distrib_width_);
00442 
00443         draw_unit(units_width_ + inter_units_gap_, damage_line_skip,
00444                   defender_left_strings_width_, defender_left_strings_, defender_right_strings_,
00445                   defender_label_, defender_label_width_, defender_hp_distrib_, defender_hp_distrib_width_);
00446     }
00447 
00448     void battle_prediction_pane::draw_unit(int x_off, int damage_line_skip, int left_strings_width,
00449                                            const std::vector<std::string>& left_strings,
00450                                            const std::vector<std::string>& right_strings,
00451                                            const std::string& label, int label_width,
00452                                            surface& hp_distrib, int hp_distrib_width)
00453     {
00454         CVideo& screen = disp_.video();
00455         int i;
00456 
00457         // NOTE. A preview pane is not made to be used alone and it is not
00458         // centered in the middle of the dialog. We "fix" this problem by moving
00459         // the clip rectangle 10 pixels to the right. This is a kludge and it
00460         // should be removed by 1) writing a custom dialog handler, or
00461         // 2) modify preview_pane so that it accepts {left, middle, right} as
00462         // layout possibilities.
00463 
00464         // Get clip rectangle and center it
00465         SDL_Rect clip_rect = location();
00466         clip_rect.x += 10;
00467 
00468         // Current vertical offset. We draw the dialog line-by-line, starting at the top.
00469         int y_off = 15;
00470 
00471         // Draw unit label.
00472         font::draw_text_line(&screen, clip_rect, font::SIZE_15, font::NORMAL_COLOUR, label,
00473                              clip_rect.x + x_off + (units_width_ - label_width) / 2, clip_rect.y + y_off, 0, TTF_STYLE_BOLD);
00474 
00475         y_off += 24;
00476 
00477         // Draw unit left and right strings except the last two (total damage and unscathed probability).
00478         for(i = 0; i < static_cast<int>(left_strings.size()) - 2; i++) {
00479             font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, left_strings[i],
00480                                  clip_rect.x + x_off, clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i,
00481                                  0, TTF_STYLE_NORMAL);
00482 
00483             font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, right_strings[i],
00484                                  clip_rect.x + x_off + left_strings_width + inter_column_gap_,
00485                                  clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i, 0, TTF_STYLE_NORMAL);
00486         }
00487 
00488         // Ensure both damage lines are aligned.
00489         y_off += damage_line_skip * (font::SIZE_NORMAL + inter_line_gap_) + 14;
00490 
00491         // Draw total damage and unscathed probability.
00492         for(i = 0; i < 2; i++) {
00493             const std::string& left_string = left_strings[left_strings.size() - 2 + i];
00494             const std::string& right_string = right_strings[right_strings.size() - 2 + i];
00495 
00496             font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, left_string,
00497                                  clip_rect.x + x_off, clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i,
00498                                  0, TTF_STYLE_NORMAL);
00499 
00500             font::draw_text_line(&screen, clip_rect, font::SIZE_NORMAL, font::NORMAL_COLOUR, right_string,
00501                                  clip_rect.x + x_off + left_strings_width + inter_column_gap_,
00502                                  clip_rect.y + y_off + (font::SIZE_NORMAL + inter_line_gap_) * i, 0, TTF_STYLE_NORMAL);
00503         }
00504 
00505         y_off += 2 * (font::SIZE_NORMAL + inter_line_gap_) + 14;
00506 
00507         // Draw hitpoints distribution string.
00508         font::draw_text(&screen, clip_rect, font::SIZE_SMALL, font::NORMAL_COLOUR, hp_distrib_string_,
00509                         clip_rect.x + x_off + (units_width_ - hp_distrib_string_width_) / 2, clip_rect.y + y_off);
00510 
00511         y_off += 19;
00512 
00513         // Draw hitpoints distributions.
00514         video().blit_surface(clip_rect.x + x_off + (units_width_ - hp_distrib_width) / 2, clip_rect.y + y_off, hp_distrib);
00515     }
00516 
00517     void battle_prediction_pane::get_hp_distrib_surface(const std::vector<std::pair<int, double> >& hp_prob_vector,
00518                                                         const battle_context::unit_stats& stats,
00519                                                         const battle_context::unit_stats& opp_stats,
00520                                                         surface& surf, int& width, int& height)
00521     {
00522         // Font size. If you change this, you must update the separator space.
00523         int fs = font::SIZE_SMALL;
00524 
00525         // Space before HP separator.
00526         int hp_sep = 24 + 6;
00527 
00528         // Bar space between both separators.
00529         int bar_space = 150;
00530 
00531         // Space after percentage separator.
00532         int percent_sep = 43 + 6;
00533 
00534         // Surface width and height.
00535         width = 30 + 2 + bar_space + 2 + percent_sep;
00536         height = 5 + (fs + 2) * hp_prob_vector.size();
00537 
00538         // Create the surface.
00539         surf = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height,
00540                                     image::pixel_format->BitsPerPixel,
00541                                 image::pixel_format->Rmask,
00542                                 image::pixel_format->Gmask,
00543                                 image::pixel_format->Bmask,
00544                                 image::pixel_format->Amask);
00545 
00546         SDL_Rect clip_rect = {0, 0, width, height};
00547         Uint32 grey_color = SDL_MapRGB(surf->format, 0xb7, 0xc1, 0xc1);
00548         Uint32 transparent_color = SDL_MapRGB(surf->format, 1, 1, 1);
00549 
00550         // Enable transparency.
00551         SDL_SetColorKey(surf, SDL_SRCCOLORKEY, transparent_color);
00552         SDL_FillRect(surf, &clip_rect, transparent_color);
00553 
00554         // Draw the surrounding borders and separators.
00555         SDL_Rect top_border_rect = {0, 0, width, 2};
00556         SDL_FillRect(surf, &top_border_rect, grey_color);
00557 
00558         SDL_Rect bottom_border_rect = {0, height - 2, width, 2};
00559         SDL_FillRect(surf, &bottom_border_rect, grey_color);
00560 
00561         SDL_Rect left_border_rect = {0, 0, 2, height};
00562         SDL_FillRect(surf, &left_border_rect, grey_color);
00563 
00564         SDL_Rect right_border_rect = {width - 2, 0, 2, height};
00565         SDL_FillRect(surf, &right_border_rect, grey_color);
00566 
00567         SDL_Rect hp_sep_rect = {hp_sep, 0, 2, height};
00568         SDL_FillRect(surf, &hp_sep_rect, grey_color);
00569 
00570         SDL_Rect percent_sep_rect = {width - percent_sep - 2, 0, 2, height};
00571         SDL_FillRect(surf, &percent_sep_rect, grey_color);
00572 
00573         // Draw the rows (lower HP values are at the bottom).
00574         for(int i = 0; i < static_cast<int>(hp_prob_vector.size()); i++) {
00575             char str_buf[10];
00576 
00577             // Get the HP and probability.
00578             int hp = hp_prob_vector[hp_prob_vector.size() - i - 1].first;
00579             double prob = hp_prob_vector[hp_prob_vector.size() - i - 1].second;
00580 
00581             SDL_Color row_color;
00582 
00583             // Death line is red.
00584             if(hp == 0) {
00585                 SDL_Color color = {0xe5, 0, 0, 0};
00586                 row_color = color;
00587             }
00588 
00589             // Below current hitpoints value is orange.
00590             else if(hp < static_cast<int>(stats.hp)) {
00591                 // Stone is grey.
00592                 if(opp_stats.stones) {
00593                     SDL_Color color = {0x9a, 0x9a, 0x9a, 0};
00594                     row_color = color;
00595                 } else {
00596                     SDL_Color color = {0xf4, 0xc9, 0, 0};
00597                     row_color = color;
00598                 }
00599             }
00600 
00601             // Current hitpoints value and above is green.
00602             else {
00603                 SDL_Color color = {0x08, 0xca, 0, 0};
00604                 row_color = color;
00605             }
00606 
00607             // Print HP, aligned right.
00608             snprintf(str_buf, 10, "%d", hp);
00609             str_buf[9] = '\0';  //prevents _snprintf error
00610             int hp_width = font::line_width(str_buf, fs);
00611 
00612             // Draw bars.
00613             font::draw_text_line(surf, clip_rect, fs, font::NORMAL_COLOUR, str_buf,
00614                                  hp_sep - hp_width - 2, 2 + (fs + 2) * i, 0, TTF_STYLE_NORMAL);
00615 
00616             int bar_len = maximum<int>(static_cast<int>((prob * (bar_space - 4)) + 0.5), 2);
00617 
00618             SDL_Rect bar_rect_1 = {hp_sep + 4, 6 + (fs + 2) * i, bar_len, 8};
00619             SDL_FillRect(surf, &bar_rect_1, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 100));
00620 
00621             SDL_Rect bar_rect_2 = {hp_sep + 4, 7 + (fs + 2) * i, bar_len, 6};
00622             SDL_FillRect(surf, &bar_rect_2, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 66));
00623 
00624             SDL_Rect bar_rect_3 = {hp_sep + 4, 8 + (fs + 2) * i, bar_len, 4};
00625             SDL_FillRect(surf, &bar_rect_3, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 33));
00626 
00627             SDL_Rect bar_rect_4 = {hp_sep + 4, 9 + (fs + 2) * i, bar_len, 2};
00628             SDL_FillRect(surf, &bar_rect_4, blend_rgb(surf, row_color.r, row_color.g, row_color.b, 0));
00629 
00630             // Draw probability percentage, aligned right.
00631             format_prob(str_buf, prob);
00632             int prob_width = font::line_width(str_buf, fs);
00633             font::draw_text_line(surf, clip_rect, fs, font::NORMAL_COLOUR, str_buf,
00634                              width - prob_width - 4, 2 + (fs + 2) * i, 0, TTF_STYLE_NORMAL);
00635         }
00636     }
00637 
00638     Uint32 battle_prediction_pane::blend_rgb(const surface& surf, unsigned char r, unsigned char g, unsigned char b, unsigned char drop)
00639     {
00640         // We simply decrement each component.
00641         if(r < drop) r = 0; else r -= drop;
00642         if(g < drop) g = 0; else g -= drop;
00643         if(b < drop) b = 0; else b -= drop;
00644 
00645         return SDL_MapRGB(surf->format, r, g, b);
00646     }
00647 
00648     // This class is used when the user clicks on the button
00649     // to show the "Damage Calculations" dialog.
00650     class attack_prediction_displayer : public gui::dialog_button_action
00651     {
00652     public:
00653         attack_prediction_displayer(game_display& disp, const std::vector<battle_context>& bc_vector, const gamemap& map,
00654                                     const std::vector<team>& teams, const unit_map& units,
00655                                     const gamestatus& status,
00656                                     const gamemap::location& attacker_loc, const gamemap::location& defender_loc)
00657                 : disp_(disp), bc_vector_(bc_vector), map_(map), teams_(teams), units_(units), status_(status),
00658                   attacker_loc_(attacker_loc), defender_loc_(defender_loc) {}
00659 
00660         // This method is called when the button is pressed.
00661         RESULT button_pressed(int selection)
00662         {
00663             // Get the selected weapon, if any.
00664             const size_t index = size_t(selection);
00665 
00666             if(index < bc_vector_.size()) {
00667                 battle_prediction_pane battle_pane(disp_, bc_vector_[index], map_, teams_, units_, status_,
00668                                                    attacker_loc_, defender_loc_);
00669                 std::vector<gui::preview_pane*> preview_panes;
00670                 preview_panes.push_back(&battle_pane);
00671 
00672                 gui::show_dialog(disp_, NULL, _("Damage Calculations"), "", gui::OK_ONLY, NULL, &preview_panes);
00673             }
00674 
00675             return gui::CONTINUE_DIALOG;
00676         }
00677 
00678     private:
00679         game_display &disp_;
00680         const std::vector<battle_context>& bc_vector_;
00681         const gamemap& map_;
00682     const std::vector<team>& teams_;
00683         const unit_map& units_;
00684         const gamestatus& status_;
00685         const gamemap::location& attacker_loc_;
00686         const gamemap::location& defender_loc_;
00687     };
00688 } //end anonymous namespace
00689 
00690 mouse_handler::mouse_handler(game_display* gui, std::vector<team>& teams, unit_map& units, gamemap& map,
00691                 gamestatus& status, undo_list& undo_stack, undo_list& redo_stack):
00692 gui_(gui), teams_(teams), units_(units), map_(map), status_(status),
00693 undo_stack_(undo_stack), redo_stack_(redo_stack)
00694 {
00695     singleton_ = this;
00696     minimap_scrolling_ = false;
00697     dragging_ = false;
00698     dragging_started_ = false;
00699     drag_from_x_ = 0;
00700     drag_from_y_ = 0;
00701     enemy_paths_ = false;
00702     path_turns_ = 0;
00703     undo_ = false;
00704     show_menu_ = false;
00705     over_route_ = false;
00706     team_num_ = 1;
00707     attackmove_ = false;
00708     reachmap_invalid_ = false;
00709     show_partial_move_ = false;
00710 }
00711 mouse_handler::~mouse_handler()
00712 {
00713     singleton_ = NULL;
00714 }
00715 
00716 void mouse_handler::set_team(const int team_number)
00717 {
00718     team_num_ = team_number;
00719 }
00720 
00721 void mouse_handler::mouse_motion(const SDL_MouseMotionEvent& event, const bool browse)
00722 {
00723     mouse_motion(event.x,event.y, browse);
00724 }
00725 
00726 void mouse_handler::mouse_update(const bool browse)
00727 {
00728     int x, y;
00729     SDL_GetMouseState(&x,&y);
00730     mouse_motion(x, y, browse, true);
00731 }
00732 
00733 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update)
00734 {
00735     if(attackmove_) return;
00736 
00737     if(minimap_scrolling_) {
00738         //if the game is run in a window, we could miss a LMB/MMB up event
00739         // if it occurs outside our window.
00740         // thus, we need to check if the LMB/MMB is still down
00741         minimap_scrolling_ = ((SDL_GetMouseState(NULL,NULL) & (SDL_BUTTON(1) | SDL_BUTTON(2))) != 0);
00742         if(minimap_scrolling_) {
00743             const gamemap::location& loc = (*gui_).minimap_location_on(x,y);
00744             if(loc.valid()) {
00745                 if(loc != last_hex_) {
00746                     last_hex_ = loc;
00747                     (*gui_).scroll_to_tile(loc,game_display::WARP,false);
00748                 }
00749             } else {
00750                 // clicking outside of the minimap will end minimap scrolling
00751                 minimap_scrolling_ = false;
00752             }
00753         }
00754         if(minimap_scrolling_) return;
00755     }
00756 
00757     const gamemap::location new_hex = (*gui_).hex_clicked_on(x,y);
00758 
00759     // Fire the drag & drop only after minimal drag distance
00760     // While we check the mouse buttons state, we also grab fresh position data.
00761     int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
00762     int my = drag_from_y_;
00763     if (dragging_ && !dragging_started_ && (SDL_GetMouseState(&mx,&my) & SDL_BUTTON_LEFT) != 0) {
00764         const double drag_distance = std::pow((double) (drag_from_x_- mx), 2) + std::pow((double) (drag_from_y_- my), 2);
00765         if (drag_distance > drag_threshold*drag_threshold) {
00766             dragging_started_ = true;
00767             cursor::set_dragging(true);
00768         }
00769     }
00770 
00771     if(new_hex != last_hex_) {
00772         update = true;
00773 
00774         if (last_hex_.valid()) {
00775             // we store the previous hexes used to propose attack direction
00776             previous_hex_ = last_hex_;
00777             // the hex of the selected unit is also "free"
00778             if (last_hex_ == selected_hex_ || find_unit(last_hex_) == units_.end()) {
00779                     previous_free_hex_ = last_hex_;
00780             }
00781         }
00782         last_hex_ = new_hex;
00783     }
00784 
00785     if (reachmap_invalid_) update = true;
00786 
00787     if (update) {
00788         if (reachmap_invalid_) {
00789             reachmap_invalid_ = false;
00790             if (!current_paths_.routes.empty() && !show_partial_move_) {
00791                 unit_map::iterator u = find_unit(selected_hex_);
00792                 if(selected_hex_.valid() && u != units_.end() ) {
00793                     // reselect the unit without firing events (updates current_paths_)
00794                     select_hex(selected_hex_, true);
00795                 }
00796                 // we do never deselect here, mainly because of canceled attack-move
00797             }
00798         }
00799 
00800 
00801 
00802         if(new_hex.valid() == false) {
00803             current_route_.steps.clear();
00804             (*gui_).set_route(NULL);
00805         }
00806 
00807         (*gui_).highlight_hex(new_hex);
00808 
00809         const unit_map::iterator selected_unit = find_unit(selected_hex_);
00810         const unit_map::iterator mouseover_unit = find_unit(new_hex);
00811 
00812         // we search if there is an attack possibility and where
00813         gamemap::location attack_from = current_unit_attacks_from(new_hex);
00814 
00815         //see if we should show the normal cursor, the movement cursor, or
00816         //the attack cursor
00817         //If the cursor is on WAIT, we don't change it and let the setter
00818         //of this state end it
00819         if (cursor::get() != cursor::WAIT) {
00820             if(selected_unit != units_.end() && selected_unit->second.side() == team_num_
00821                && !selected_unit->second.incapacitated() && !browse) {
00822                 if (attack_from.valid()) {
00823                     cursor::set(dragging_started_ ? cursor::ATTACK_DRAG : cursor::ATTACK);
00824                 } else if (mouseover_unit==units_.end() && current_paths_.routes.count(new_hex)) {
00825                     cursor::set(dragging_started_ ? cursor::MOVE_DRAG : cursor::MOVE);
00826                 } else {
00827                     // selecte unit can't attack or move there
00828                     cursor::set(cursor::NORMAL);
00829                 }
00830             } else {
00831                 // no selected unit or we can't move it
00832                 cursor::set(cursor::NORMAL);
00833             }
00834         }
00835 
00836         // show (or cancel) the attack direction indicator
00837         if (attack_from.valid() && !browse) {
00838             gui_->set_attack_indicator(attack_from, new_hex);
00839         } else {
00840             gui_->clear_attack_indicator();
00841         }
00842 
00843         if(enemy_paths_) {
00844             enemy_paths_ = false;
00845             current_paths_ = paths();
00846             gui_->unhighlight_reach();
00847         } else if(over_route_) {
00848             over_route_ = false;
00849             current_route_.steps.clear();
00850             (*gui_).set_route(NULL);
00851         }
00852 
00853         // the destination is the pointed hex or the adjacent hex
00854         // used to attack it
00855         gamemap::location dest;
00856         unit_map::const_iterator dest_un;
00857         if (attack_from.valid()) {
00858             dest = attack_from;
00859             dest_un = find_unit(dest);
00860         }   else {
00861             dest = new_hex;
00862             dest_un = mouseover_unit;
00863         }
00864 
00865         if(dest == selected_hex_ || dest_un != units_.end()) {
00866             current_route_.steps.clear();
00867             (*gui_).set_route(NULL);
00868         } else if(!current_paths_.routes.empty() && map_.on_board(selected_hex_) &&
00869            map_.on_board(new_hex)) {
00870 
00871             if(selected_unit != units_.end() && !selected_unit->second.incapacitated()) {
00872                 // the movement_reset is active only if it's not the unit's turn
00873                 unit_movement_resetter move_reset(selected_unit->second,
00874                         selected_unit->second.side() != team_num_);
00875                 current_route_ = get_route(selected_unit, dest, viewing_team());
00876                 if(!browse) {
00877                     (*gui_).set_route(&current_route_);
00878                 }
00879             }
00880         }
00881 
00882         unit_map::iterator un = mouseover_unit;
00883 
00884         if(un != units_.end() && current_paths_.routes.empty() && !(*gui_).fogged(un->first)) {
00885             if (un->second.side() != team_num_) {
00886                 //unit under cursor is not on our team, highlight reach
00887                 unit_movement_resetter move_reset(un->second);
00888 
00889                 const bool teleport = un->second.get_ability_bool("teleport",un->first);
00890                 current_paths_ = paths(map_,units_,new_hex,teams_,
00891                                     false,teleport,viewing_team(),path_turns_);
00892                 gui_->highlight_reach(current_paths_);
00893                 enemy_paths_ = true;
00894             } else {
00895                 //unit is on our team, show path if the unit has one
00896                 const gamemap::location go_to = un->second.get_goto();
00897                 if(map_.on_board(go_to)) {
00898                     paths::route route = get_route(un, go_to, current_team());
00899                     gui_->set_route(&route);
00900                 }
00901                 over_route_ = true;
00902             }
00903         }
00904     }
00905 }
00906 
00907 unit_map::iterator mouse_handler::selected_unit()
00908 {
00909     unit_map::iterator res = find_unit(selected_hex_);
00910     if(res != units_.end()) {
00911         return res;
00912     } else {
00913         return find_unit(last_hex_);
00914     }
00915 }
00916 
00917 unit_map::iterator mouse_handler::find_unit(const gamemap::location& hex)
00918 {
00919     return find_visible_unit(units_,hex,map_,teams_,viewing_team());
00920 }
00921 
00922 unit_map::const_iterator mouse_handler::find_unit(const gamemap::location& hex) const
00923 {
00924     return find_visible_unit(units_,hex,map_,teams_,viewing_team());
00925 }
00926 
00927 gamemap::location mouse_handler::current_unit_attacks_from(const gamemap::location& loc)
00928 {
00929     const unit_map::const_iterator current = find_unit(selected_hex_);
00930     if(current == units_.end() || current->second.side() != team_num_
00931         || current->second.attacks_left()==0) {
00932         return gamemap::location();
00933     }
00934 
00935     const unit_map::const_iterator enemy = find_unit(loc);
00936     if(enemy == units_.end() || current_team().is_enemy(enemy->second.side()) == false
00937         || enemy->second.incapacitated())
00938     {
00939         return gamemap::location();
00940     }
00941 
00942     const gamemap::location::DIRECTION preferred = loc.get_relative_dir(previous_hex_);
00943     const gamemap::location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);
00944 
00945     int best_rating = 100;//smaller is better
00946     gamemap::location res;
00947     gamemap::location adj[6];
00948     get_adjacent_tiles(loc,adj);
00949 
00950     for(size_t n = 0; n != 6; ++n) {
00951         if(map_.on_board(adj[n]) == false) {
00952             continue;
00953         }
00954 
00955         if(adj[n] != selected_hex_ && find_unit(adj[n]) != units_.end()) {
00956             continue;
00957         }
00958 
00959         if(current_paths_.routes.count(adj[n])) {
00960             static const size_t NDIRECTIONS = gamemap::location::NDIRECTIONS;
00961             unsigned int difference = abs(int(preferred - n));
00962             if(difference > NDIRECTIONS/2) {
00963                 difference = NDIRECTIONS - difference;
00964             }
00965             unsigned int second_difference = abs(int(second_preferred - n));
00966             if(second_difference > NDIRECTIONS/2) {
00967                 second_difference = NDIRECTIONS - second_difference;
00968             }
00969             const int rating = difference * 2 + (second_difference > difference);
00970             if(rating < best_rating || res.valid() == false) {
00971                 best_rating = rating;
00972                 res = adj[n];
00973             }
00974         }
00975     }
00976 
00977     return res;
00978 }
00979 
00980 paths::route mouse_handler::get_route(unit_map::const_iterator un, gamemap::location go_to, team &team)
00981 {
00982     // The pathfinder will check unit visibility (fogged/stealthy).
00983     const shortest_path_calculator calc(un->second,team,units_,teams_,map_);
00984 
00985     std::set<gamemap::location> allowed_teleports;
00986 
00987     if(un->second.get_ability_bool("teleport",un->first)) {
00988         // search all known empty friendly villages
00989         for(std::set<gamemap::location>::const_iterator i = team.villages().begin();
00990                 i != team.villages().end(); ++i) {
00991             if (viewing_team().is_enemy(un->second.side()) && viewing_team().fogged(*i))
00992                 continue;
00993 
00994             unit_map::const_iterator occupant = find_unit(*i);
00995             if (occupant != units_.end() && occupant != un)
00996                 continue;
00997 
00998             allowed_teleports.insert(*i);
00999         }
01000     }
01001 
01002     paths::route route = a_star_search(un->first, go_to, 10000.0, &calc, map_.w(), map_.h(), &allowed_teleports);
01003 
01004     route_turns_to_complete(un->second, route, viewing_team(), units_,teams_,map_);
01005 
01006     return route;
01007 }
01008 
01009 void mouse_handler::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
01010 {
01011     if(attackmove_) return;
01012     show_menu_ = false;
01013     mouse_update(browse);
01014     int scrollx = 0;
01015     int scrolly = 0;
01016 
01017     if(is_left_click(event) && event.state == SDL_RELEASED) {
01018         minimap_scrolling_ = false;
01019         dragging_ = false;
01020         cursor::set_dragging(false);
01021         if (dragging_started_ && !browse && !commands_disabled) {
01022             left_click(event, browse);
01023         }
01024         dragging_started_= false;
01025     } else if(is_middle_click(event) && event.state == SDL_RELEASED) {
01026         minimap_scrolling_ = false;
01027     } else if(is_left_click(event) && event.state == SDL_PRESSED) {
01028         left_click(event, browse);
01029         if (!browse && !commands_disabled) {
01030             dragging_ = true;
01031             dragging_started_ = false;
01032             SDL_GetMouseState(&drag_from_x_, &drag_from_y_);
01033         }
01034     } else if(is_right_click(event) && event.state == SDL_PRESSED) {
01035         // The first right-click cancel the selection if any,
01036         // the second open the context menu
01037         dragging_ = false;
01038         dragging_started_ = false;
01039         cursor::set_dragging(false);
01040         if (selected_hex_.valid() && find_unit(selected_hex_) != units_.end()) {
01041             select_hex(gamemap::location(), browse);
01042         } else {
01043             gui_->draw(); // redraw highlight (and maybe some more)
01044             const theme::menu* const m = gui_->get_theme().context_menu();
01045             if (m != NULL)
01046                 show_menu_ = true;
01047             else
01048                 LOG_STREAM(warn, display) << "no context menu found...\n";
01049         }
01050     } else if(is_middle_click(event) && event.state == SDL_PRESSED) {
01051         // clicked on a hex on the minimap? then initiate minimap scrolling
01052         const gamemap::location& loc = gui_->minimap_location_on(event.x,event.y);
01053         minimap_scrolling_ = false;
01054         if(loc.valid()) {
01055             minimap_scrolling_ = true;
01056             last_hex_ = loc;
01057             gui_->scroll_to_tile(loc,game_display::WARP,false);
01058         }
01059     } else if (event.button == SDL_BUTTON_WHEELUP) {
01060         scrolly = - preferences::scroll_speed();
01061     } else if (event.button == SDL_BUTTON_WHEELDOWN) {
01062         scrolly = preferences::scroll_speed();
01063     } else if (event.button == SDL_BUTTON_WHEELLEFT) {
01064         scrollx = - preferences::scroll_speed();
01065     } else if (event.button == SDL_BUTTON_WHEELRIGHT) {
01066         scrollx = preferences::scroll_speed();
01067     }
01068 
01069     if (scrollx != 0 || scrolly != 0) {
01070         CKey pressed;
01071         // Alt + mousewheel do an 90° rotation on the scroll direction
01072         if (pressed[SDLK_LALT] || pressed[SDLK_RALT])
01073             gui_->scroll(scrolly,scrollx);
01074         else
01075             gui_->scroll(scrollx,scrolly);
01076     }
01077 
01078     if (!dragging_ && dragging_started_) {
01079         dragging_started_ = false;
01080         cursor::set_dragging(false);
01081     }
01082 
01083     mouse_update(browse);
01084 
01085 }
01086 
01087 bool mouse_handler::is_left_click(const SDL_MouseButtonEvent& event)
01088 {
01089     return event.button == SDL_BUTTON_LEFT && !command_active();
01090 }
01091 
01092 bool mouse_handler::is_middle_click(const SDL_MouseButtonEvent& event)
01093 {
01094     return event.button == SDL_BUTTON_MIDDLE;
01095 }
01096 
01097 bool mouse_handler::is_right_click(const SDL_MouseButtonEvent& event)
01098 {
01099     return event.button == SDL_BUTTON_RIGHT || (event.button == SDL_BUTTON_LEFT && command_active());
01100 }
01101 
01102 void mouse_handler::left_click(const SDL_MouseButtonEvent& event, const bool browse)
01103 {
01104     dragging_ = false;
01105     dragging_started_ = false;
01106     cursor::set_dragging(false);
01107     undo_ = false;
01108     bool check_shroud = teams_[team_num_ - 1].auto_shroud_updates();
01109 
01110     // clicked on a hex on the minimap? then initiate minimap scrolling
01111     const gamemap::location& loc = gui_->minimap_location_on(event.x,event.y);
01112     minimap_scrolling_ = false;
01113     if(loc.valid()) {
01114         minimap_scrolling_ = true;
01115         last_hex_ = loc;
01116         gui_->scroll_to_tile(loc,game_display::WARP,false);
01117         return;
01118     }
01119 
01120     //we use the last registered highlighted hex
01121     //since it's what update our global state
01122     gamemap::location hex = last_hex_;
01123 
01124     unit_map::iterator u = find_unit(selected_hex_);
01125 
01126     //if the unit is selected and then itself clicked on,
01127     //any goto command is cancelled
01128     if(u != units_.end() && !browse && selected_hex_ == hex && u->second.side() == team_num_) {
01129         u->second.set_goto(gamemap::location());
01130     }
01131 
01132     unit_map::iterator clicked_u = find_unit(hex);
01133 
01134     const gamemap::location src = selected_hex_;
01135     paths orig_paths = current_paths_;
01136     const gamemap::location& attack_from = current_unit_attacks_from(hex);
01137 
01138     //see if we're trying to do a attack or move-and-attack
01139     if(!browse && !commands_disabled && attack_from.valid()) {
01140         if (attack_from == selected_hex_) { //no move needed
01141             if (attack_enemy(u, clicked_u) == false) {
01142                 return;
01143             }
01144         }
01145         else if (move_unit_along_current_route(false, true)) {//move the unit without updating shroud
01146             // a WML event could have invalidated both attacker and defender
01147             // so make sure they're valid before attacking
01148             u = find_unit(attack_from);
01149             unit_map::iterator enemy = find_unit(hex);
01150             if(u != units_.end() && u->second.side() == team_num_ &&
01151                 enemy != units_.end() && current_team().is_enemy(enemy->second.side()) && !enemy->second.incapacitated()) {
01152                 //if shroud or fog is active, rememember units and after attack check if someone isn't seen
01153                 std::set<gamemap::location> known_units;
01154 
01155                 if (teams_[team_num_-1].uses_shroud() || teams_[team_num_-1].uses_fog()){
01156                      for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
01157                    if(teams_[team_num_-1].fogged(u->first) == false) {
01158                      known_units.insert(u->first);
01159                      teams_[team_num_-1].see(u->second.side()-1);
01160                             }
01161                         }
01162                 }
01163 
01164                 // reselect the unit to make the attacker's stats appear during the attack dialog
01165                 gui_->select_hex(attack_from);
01166 
01167                 if(!commands_disabled && attack_enemy(u,enemy) == false) {
01168                     undo_ = true;
01169                     selected_hex_ = src;
01170                     gui_->select_hex(src);
01171                     current_paths_ = orig_paths;
01172                     gui_->highlight_reach(current_paths_);
01173                     return;
01174                 }
01175                 else //attack == true
01176                 {
01177                     if (teams_[team_num_-1].uses_shroud() || teams_[team_num_-1].uses_fog()){
01178                         //check if some new part of map discovered or is active delay shroud updates, which need special care
01179                         if (clear_shroud(*gui_, map_, units_, teams_, team_num_ - 1)||!teams_[team_num_-1].auto_shroud_updates()){
01180                             clear_undo_stack();
01181                             gui_->invalidate_all();
01182                             gui_->recalculate_minimap();
01183                             gui_->draw();
01184                             //some new part of map discovered
01185                             for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
01186                                 if(teams_[team_num_-1].fogged(u->first) == false) {
01187                                     //check if unit is not known
01188                                     if (known_units.find(u->first)==known_units.end())
01189                                     {
01190                                         game_events::raise("sighted",u->first,attack_from);
01191                                     }
01192                                  }
01193                             }
01194                             game_events::pump();
01195                             return;
01196                         }
01197                     }
01198                 }
01199             }
01200         }
01201 
01202         if(check_shroud && clear_shroud(*gui_, map_, units_, teams_, team_num_ - 1)) {
01203             clear_undo_stack();
01204             gui_->invalidate_all();
01205             gui_->draw();
01206         }
01207 
01208         return;
01209     }
01210 
01211     //otherwise we're trying to move to a hex
01212     else if(!commands_disabled && !browse && selected_hex_.valid() && selected_hex_ != hex &&
01213              u != units_.end() && u->second.side() == team_num_ &&
01214              clicked_u == units_.end() && !current_route_.steps.empty() &&
01215              current_route_.steps.front() == selected_hex_) {
01216 
01217         gui_->unhighlight_reach();
01218         move_unit_along_current_route(check_shroud);
01219     } else {
01220         // we select a (maybe empty) hex
01221         select_hex(hex, browse);
01222     }
01223 }
01224 
01225 void mouse_handler::select_hex(const gamemap::location& hex, const bool browse) {
01226     selected_hex_ = hex;
01227     gui_->select_hex(hex);
01228     gui_->clear_attack_indicator();
01229     gui_->set_route(NULL);
01230     show_partial_move_ = false;
01231 
01232     unit_map::iterator u = find_unit(hex);
01233     if(hex.valid() && u != units_.end() ) {
01234         next_unit_ = u->first;
01235 
01236         // if it's not the unit's turn, we reset its moves
01237         unit_movement_resetter move_reset(u->second, u->second.side() != team_num_);
01238         const bool teleport = u->second.get_ability_bool("teleport",u->first);
01239         current_paths_ = paths(map_,units_,hex,teams_,
01240                            false,teleport,viewing_team(),path_turns_);
01241         show_attack_options(u);
01242         gui_->highlight_reach(current_paths_);
01243         // the highlight now comes from selection
01244         // and not from the mouseover on an enemy
01245         enemy_paths_ = false;
01246         gui_->set_route(NULL);
01247 
01248         // selection have impact only if we are not observing and it's our unit
01249         if (!browse && !commands_disabled && u->second.side() == gui_->viewing_team()+1) {
01250             sound::play_UI_sound("select-unit.wav");
01251             u->second.set_selecting(*gui_, u->first);
01252 
01253             game_events::fire("select", hex);
01254         }
01255 
01256     } else {
01257         gui_->unhighlight_reach();
01258         current_paths_ = paths();
01259         current_route_.steps.clear();
01260     }
01261 }
01262 
01263 void mouse_handler::deselect_hex() {
01264     select_hex(gamemap::location(), true);
01265 }
01266 
01267 void mouse_handler::clear_undo_stack()
01268 {
01269     if(teams_[team_num_ - 1].auto_shroud_updates() == false)
01270         apply_shroud_changes(undo_stack_,gui_,map_,units_,teams_,team_num_-1);
01271     undo_stack_.clear();
01272 }
01273 
01274 bool mouse_handler::move_unit_along_current_route(bool check_shroud, bool attackmove)
01275 {
01276     const std::vector<gamemap::location> steps = current_route_.steps;
01277     if(steps.empty()) {
01278         return false;
01279     }
01280 
01281     // do not show footsteps during movement
01282     gui_->set_route(NULL);
01283 
01284     // do not keep the hex highlighted that we started from
01285     selected_hex_ = gamemap::location();
01286     gui_->select_hex(gamemap::location());
01287 
01288     // will be invalid after the move
01289     current_paths_ = paths();
01290     current_route_.steps.clear();
01291 
01292     attackmove_ = attackmove;
01293     const size_t moves = ::move_unit(gui_,map_,units_,teams_,
01294                        steps,&recorder,&undo_stack_,&next_unit_,false,check_shroud);
01295     attackmove_ = false;
01296 
01297     cursor::set(cursor::NORMAL);
01298 
01299     gui_->invalidate_game_status();
01300 
01301     if(moves == 0)
01302         return false;
01303 
01304     redo_stack_.clear();
01305 
01306     assert(moves <= steps.size());
01307     const gamemap::location& dst = steps[moves-1];
01308     const unit_map::const_iterator u = units_.find(dst);
01309 
01310     //u may be equal to units_.end() in the case of e.g. a [teleport]
01311     if(u != units_.end()) {
01312         if(dst != steps.back()) {
01313             // the move was interrupted (or never started)
01314             if (u->second.movement_left() > 0) {
01315                 // reselect the unit (for "press t to continue")
01316                 select_hex(dst, false);
01317                 // the new discovery is more important than the new movement range
01318                 show_partial_move_ = true;
01319                 gui_->unhighlight_reach();
01320             }
01321         }
01322     }
01323 
01324     return moves == steps.size();
01325 }
01326 
01327 bool mouse_handler::attack_enemy(unit_map::iterator attacker, unit_map::iterator defender)
01328 {
01329     try {
01330         return attack_enemy_(attacker, defender);
01331     } catch(std::bad_alloc) {
01332         lg::wml_error << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
01333         return false;
01334     }
01335 
01336 }
01337 
01338 bool mouse_handler::attack_enemy_(unit_map::iterator attacker, unit_map::iterator defender)
01339 {
01340     //we must get locations by value instead of by references, because the iterators
01341     //may become invalidated later
01342     const gamemap::location attacker_loc = attacker->first;
01343     const gamemap::location defender_loc = defender->first;
01344 
01345     std::vector<std::string> items;
01346 
01347     std::vector<battle_context> bc_vector;
01348     unsigned int i, best = 0;
01349     for (i = 0; i < attacker->second.attacks().size(); i++) {
01350         // skip weapons with attack_weight=0
01351         if (attacker->second.attacks()[i].attack_weight() > 0) {
01352             battle_context bc(map_, teams_, units_, status_, attacker->first, defender->first, i);
01353             bc_vector.push_back(bc);
01354             if (bc.better_attack(bc_vector[best], 0.5)) {
01355                 best = i;
01356             }
01357         }
01358     }
01359 
01360     for (i = 0; i < bc_vector.size(); i++) {
01361         const battle_context::unit_stats& att = bc_vector[i].get_attacker_stats();
01362         const battle_context::unit_stats& def = bc_vector[i].get_defender_stats();
01363         config tmp_config;
01364         attack_type no_weapon(tmp_config);
01365         const attack_type& attw = attack_type(*att.weapon);
01366         const attack_type& defw = attack_type(def.weapon ? *def.weapon : no_weapon);
01367 
01368         attw.set_specials_context(attacker->first, defender->first, attacker->second, true);
01369         defw.set_specials_context(attacker->first, defender->first, attacker->second, false);
01370 
01371         //if there is an attack special or defend special, we output a single space for the other unit, to make sure
01372         //that the attacks line up nicely.
01373         std::string special_pad = "";
01374         if (!attw.weapon_specials().empty() || !defw.weapon_specials().empty())
01375             special_pad = " ";
01376 
01377         std::stringstream atts;
01378         if (i == best) {
01379             atts << DEFAULT_ITEM;
01380         }
01381 
01382         std::string range = attw.range().empty() ? defw.range() : attw.range();
01383         if (!range.empty()) {
01384             range = gettext(range.c_str());
01385         }
01386         atts << IMAGE_PREFIX << attw.icon() << COLUMN_SEPARATOR
01387              << font::BOLD_TEXT << attw.name() << "\n" << att.damage << "-"
01388              << att.num_blows << " "  << " (" << att.chance_to_hit << "%)\n"
01389              << attw.weapon_specials() << special_pad
01390              << COLUMN_SEPARATOR << "<245,230,193>" << "- " << range << " -" << COLUMN_SEPARATOR
01391              << font::BOLD_TEXT << defw.name() << "\n" << def.damage << "-"
01392              << def.num_blows << " "  << " (" << def.chance_to_hit << "%)\n"
01393              << defw.weapon_specials() << special_pad << COLUMN_SEPARATOR
01394              << IMAGE_PREFIX << defw.icon();
01395 
01396         items.push_back(atts.str());
01397     }
01398 
01399     //make it so that when we attack an enemy, the attacking unit
01400     //is again shown in the status bar, so that we can easily
01401     //compare between the attacking and defending unit
01402     gui_->highlight_hex(gamemap::location());
01403     gui_->draw(true,true);
01404 
01405     attack_prediction_displayer ap_displayer(*gui_, bc_vector, map_, teams_, units_, status_, attacker_loc, defender_loc);
01406     std::vector<gui::dialog_button_info> buttons;
01407     buttons.push_back(gui::dialog_button_info(&ap_displayer, _("Damage Calculations")));
01408 
01409     int res = 0;
01410 
01411     {
01412         dialogs::units_list_preview_pane attacker_preview(*gui_,&map_,attacker->second,dialogs::unit_preview_pane::SHOW_BASIC,true);
01413         dialogs::units_list_preview_pane defender_preview(*gui_,&map_,defender->second,dialogs::unit_preview_pane::SHOW_BASIC,false);
01414         std::vector<gui::preview_pane*> preview_panes;
01415         preview_panes.push_back(&attacker_preview);
01416         preview_panes.push_back(&defender_preview);
01417 
01418         res = gui::show_dialog(*gui_,NULL,_("Attack Enemy"),
01419                 _("Choose weapon:")+std::string("\n"),
01420                 gui::OK_CANCEL,&items,&preview_panes,"",NULL,-1,NULL,-1,-1,
01421                 NULL,&buttons);
01422     }
01423 
01424     cursor::set(cursor::NORMAL);
01425     if(size_t(res) < bc_vector.size()) {
01426         const battle_context::unit_stats &att = bc_vector[res].get_attacker_stats();
01427         const battle_context::unit_stats &def = bc_vector[res].get_defender_stats();
01428 
01429         attacker->second.set_goto(gamemap::location());
01430         clear_undo_stack();
01431         redo_stack_.clear();
01432 
01433         current_paths_ = paths();
01434         gui_->clear_attack_indicator();
01435         gui_->unhighlight_reach();
01436 
01437         gui_->draw();
01438 
01439         const bool defender_human = teams_[defender->second.side()-1].is_human();
01440 
01441         recorder.add_attack(attacker_loc,defender_loc,att.attack_num,def.attack_num);
01442 
01443         //MP_COUNTDOWN grant time bonus for attacking
01444         current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
01445 
01446         try {
01447             attack(*gui_,map_,teams_,attacker_loc,defender_loc,att.attack_num,def.attack_num,units_,status_);
01448         } catch(end_level_exception&) {
01449             //if the level ends due to a unit being killed, still see if
01450             //either the attacker or defender should advance
01451             dialogs::advance_unit(map_,units_,attacker_loc,*gui_);
01452             dialogs::advance_unit(map_,units_,defender_loc,*gui_,!defender_human);
01453             throw;
01454         }
01455 
01456         dialogs::advance_unit(map_,units_,attacker_loc,*gui_);
01457         dialogs::advance_unit(map_,units_,defender_loc,*gui_,!defender_human);
01458 
01459         check_victory(units_, teams_, *gui_);
01460 
01461         gui_->draw();
01462 
01463         return true;
01464     } else {
01465         return false;
01466     }
01467 }
01468 
01469 void mouse_handler::show_attack_options(unit_map::const_iterator u)
01470 {
01471     team& current_team = teams_[team_num_-1];
01472 
01473     if(u == units_.end() || u->second.attacks_left() == 0)
01474         return;
01475 
01476     for(unit_map::const_iterator target = units_.begin(); target != units_.end(); ++target) {
01477         if(current_team.is_enemy(target->second.side()) &&
01478             distance_between(target->first,u->first) == 1 && !target->second.incapacitated()) {
01479             current_paths_.routes[target->first] = paths::route();
01480         }
01481     }
01482 }
01483 
01484 bool mouse_handler::unit_in_cycle(unit_map::const_iterator it)
01485 {
01486     if (it == units_.end())
01487         return false;
01488 
01489     if(it->second.side() != team_num_ || it->second.user_end_turn()
01490             || gui_->fogged(it->first) || !unit_can_move(it->first,units_,map_,teams_))
01491         return false;
01492 
01493     if (current_team().is_enemy(int(gui_->viewing_team()+1)) &&
01494             it->second.invisible(it->first,units_,teams_))
01495         return false;
01496 
01497     return true;
01498 
01499 }
01500 
01501 void mouse_handler::cycle_units(const bool browse, const bool reverse)
01502 {
01503     if (units_.begin() == units_.end()) {
01504         return;
01505     }
01506 
01507     unit_map::const_iterator it = find_unit(next_unit_);
01508     if (it == units_.end())
01509         it = units_.begin();
01510     const unit_map::const_iterator itx = it;
01511 
01512     do {
01513         if (reverse) {
01514             if (it == units_.begin())
01515                 it = units_.end();
01516             --it;
01517         } else {
01518             if (it == units_.end())
01519                 it = units_.begin();
01520             else
01521                 ++it;
01522         }
01523     } while (it != itx && !unit_in_cycle(it));
01524 
01525     if (unit_in_cycle(it)) {
01526         gui_->scroll_to_tile(it->first,game_display::WARP);
01527         select_hex(it->first, browse);
01528         mouse_update(browse);
01529     }
01530 }
01531 
01532 void mouse_handler::set_current_paths(paths new_paths) {
01533     gui_->unhighlight_reach();
01534     current_paths_ = new_paths;
01535     current_route_.steps.clear();
01536     gui_->set_route(NULL);
01537 }
01538 
01539 mouse_handler *mouse_handler::singleton_ = NULL;
01540 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs