play_controller.cpp

Go to the documentation of this file.
00001 /* $Id: play_controller.cpp 26243 2008-04-30 03:57:31Z alink $ */
00002 /*
00003    Copyright (C) 2006 - 2008 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
00004    wesnoth playlevel 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 //! @file play_controller.cpp
00017 //! Handle input via mouse & keyboard, events, schedule commands.
00018 
00019 #include "play_controller.hpp"
00020 #include "dialogs.hpp"
00021 #include "config_adapter.hpp"
00022 #include "game_display.hpp"
00023 #include "game_errors.hpp"
00024 #include "gettext.hpp"
00025 #include "loadscreen.hpp"
00026 #include "log.hpp"
00027 #include "sound.hpp"
00028 #include "team.hpp"
00029 #include "terrain_filter.hpp"
00030 #include "variable.hpp"
00031 
00032 #include <cassert>
00033 
00034 #define LOG_NG LOG_STREAM(info, engine)
00035 
00036 play_controller::play_controller(const config& level,
00037     game_state& state_of_game, int ticks, int num_turns, const config& game_config,
00038     CVideo& video, bool skip_replay, bool is_replay) :
00039     verify_manager_(units_), team_manager_(teams_), labels_manager_(),
00040     help_manager_(&game_config, &map_), mouse_handler_(gui_, teams_,
00041         units_, map_, status_, undo_stack_, redo_stack_),
00042     menu_handler_(gui_, units_, teams_, level, map_, game_config,
00043         status_, state_of_game, undo_stack_, redo_stack_),
00044     generator_setter(&recorder), statistics_context_(level["name"]),
00045     level_(level), game_config_(game_config),
00046     gamestate_(state_of_game), status_(level, num_turns, &state_of_game),
00047     map_(game_config, level["map_data"]), ticks_(ticks),
00048     xp_mod_(atoi(level["experience_modifier"].c_str()) > 0 ? atoi(level["experience_modifier"].c_str()) : 100),
00049     loading_game_(level["playing_team"].empty() == false),
00050     first_human_team_(-1), player_number_(1),
00051     first_player_ (lexical_cast_default<unsigned int,std::string>(level_["playing_team"], 0) + 1),
00052     start_turn_(status_.turn()), is_host_(true), skip_replay_(skip_replay),
00053     browse_(false), linger_(false), scrolling_(false)
00054 {
00055     status_.teams = &teams_;
00056     game_config::add_color_info(level);
00057 
00058     init(video, is_replay);
00059 }
00060 
00061 play_controller::~play_controller(){
00062     delete halo_manager_;
00063     delete prefs_disp_manager_;
00064     delete tooltips_manager_;
00065     delete events_manager_;
00066     delete soundsources_manager_;
00067     delete gui_;
00068 }
00069 
00070 void play_controller::init(CVideo& video, bool is_replay){
00071     loadscreen::global_loadscreen_manager loadscreen_manager(video);
00072 
00073     // If the recorder has no event, adds an "game start" event
00074     // to the recorder, whose only goal is to initialize the RNG
00075     if(recorder.empty()) {
00076         recorder.add_start();
00077     } else {
00078         recorder.pre_replay();
00079     }
00080     recorder.set_skip(false);
00081 
00082     const config::child_list& unit_cfg = level_.get_children("side");
00083     const bool snapshot = utils::string_bool(level_["snapshot"]);
00084 
00085     if(utils::string_bool(level_["modify_placing"])) {
00086         LOG_NG << "modifying placing...\n";
00087         place_sides_in_preferred_locations(map_,unit_cfg);
00088     }
00089 
00090     loadscreen::global_loadscreen->set_progress(70, _("Initializing teams"));
00091 
00092     LOG_NG << "initializing teams..." << unit_cfg.size() << "\n";;
00093     LOG_NG << (SDL_GetTicks() - ticks_) << "\n";
00094 
00095     std::set<std::string> seen_save_ids;
00096 
00097     for(config::child_list::const_iterator ui = unit_cfg.begin(); ui != unit_cfg.end(); ++ui) {
00098         std::string save_id = get_unique_saveid(**ui, seen_save_ids);
00099         seen_save_ids.insert(save_id);
00100         if (first_human_team_ == -1){
00101             first_human_team_ = get_first_human_team(ui, unit_cfg);
00102         }
00103         get_player_info(**ui, gamestate_, save_id, teams_, level_, map_, units_, status_, snapshot, is_replay );
00104     }
00105 
00106     loadscreen::global_loadscreen->set_progress(80, _("Initializing display"));
00107 
00108     preferences::encounter_recruitable_units(teams_);
00109     preferences::encounter_start_units(units_);
00110     preferences::encounter_recallable_units(gamestate_);
00111     preferences::encounter_map_terrain(map_);
00112 
00113     LOG_NG << "initialized teams... "    << (SDL_GetTicks() - ticks_) << "\n";
00114     LOG_NG << "initializing display... " << (SDL_GetTicks() - ticks_) << "\n";
00115 
00116     const config* theme_cfg = get_theme(game_config_, level_["theme"]);
00117     if (theme_cfg)
00118         gui_ = new game_display(units_,video,map_,status_,teams_,*theme_cfg, game_config_, level_);
00119     else
00120         gui_ = new game_display(units_,video,map_,status_,teams_,config(), game_config_, level_);
00121     loadscreen::global_loadscreen->set_progress(90, _("Initializing display"));
00122     mouse_handler_.set_gui(gui_);
00123     menu_handler_.set_gui(gui_);
00124     theme::set_known_themes(&game_config_);
00125 
00126     LOG_NG << "done initializing display... " << (SDL_GetTicks() - ticks_) << "\n";
00127 
00128     if(first_human_team_ != -1) {
00129         gui_->set_team(first_human_team_);
00130     }
00131     else if (team_manager_.is_observer())
00132     {
00133         // Find first team that is allowed to be observered.
00134         // If not set here observer would be without fog untill
00135         // the first turn of observerable side
00136         size_t i;
00137         for (i=0;i < teams_.size();++i)
00138         {
00139             if (!teams_[i].get_disallow_observers())
00140             {
00141                 gui_->set_team(i);
00142             }
00143         }
00144     }
00145 
00146     init_managers();
00147     loadscreen::global_loadscreen->set_progress(100, _("Starting game"));
00148     loadscreen::global_loadscreen = NULL;
00149 }
00150 
00151 void play_controller::init_managers(){
00152     LOG_NG << "initializing managers... " << (SDL_GetTicks() - ticks_) << "\n";
00153     prefs_disp_manager_ = new preferences::display_manager(gui_);
00154     tooltips_manager_ = new tooltips::manager(gui_->video());
00155     soundsources_manager_ = new soundsource::manager(*gui_);
00156 
00157     // This *needs* to be created before the show_intro and show_map_scene
00158     // as that functions use the manager state_of_game
00159     events_manager_ = new game_events::manager(level_,*gui_,map_, *soundsources_manager_,
00160                                                    units_,teams_, gamestate_,status_);
00161 
00162     halo_manager_ = new halo::manager(*gui_);
00163     LOG_NG << "done initializing managers... " << (SDL_GetTicks() - ticks_) << "\n";
00164 }
00165 
00166 static int placing_score(const config& side, const gamemap& map, const gamemap::location& pos)
00167 {
00168     int positions = 0, liked = 0;
00169     const t_translation::t_list terrain = t_translation::read_list(side["terrain_liked"]);
00170 
00171     for(int i = pos.x-8; i != pos.x+8; ++i) {
00172         for(int j = pos.y-8; j != pos.y+8; ++j) {
00173             const gamemap::location pos(i,j);
00174             if(map.on_board(pos)) {
00175                 ++positions;
00176                 if(std::count(terrain.begin(),terrain.end(),map[pos])) {
00177                     ++liked;
00178                 }
00179             }
00180         }
00181     }
00182 
00183     return (100*liked)/positions;
00184 }
00185 
00186 struct placing_info {
00187     int side, score;
00188     gamemap::location pos;
00189 };
00190 
00191 static bool operator<(const placing_info& a, const placing_info& b) { return a.score > b.score; }
00192 
00193 void play_controller::place_sides_in_preferred_locations(gamemap& map, const config::child_list& sides)
00194 {
00195     std::vector<placing_info> placings;
00196 
00197     const int num_pos = map.num_valid_starting_positions();
00198 
00199     for(config::child_list::const_iterator s = sides.begin(); s != sides.end(); ++s) {
00200         const int side_num = s - sides.begin() + 1;
00201         for(int p = 1; p <= num_pos; ++p) {
00202             const gamemap::location& pos = map.starting_position(p);
00203             const int score = placing_score(**s,map,pos);
00204             placing_info obj;
00205             obj.side = side_num;
00206             obj.score = score;
00207             obj.pos = pos;
00208             placings.push_back(obj);
00209         }
00210     }
00211 
00212     std::sort(placings.begin(),placings.end());
00213     std::set<int> placed;
00214     std::set<gamemap::location> positions_taken;
00215 
00216     for(std::vector<placing_info>::const_iterator i = placings.begin(); i != placings.end() && placed.size() != sides.size(); ++i) {
00217         if(placed.count(i->side) == 0 && positions_taken.count(i->pos) == 0) {
00218             placed.insert(i->side);
00219             positions_taken.insert(i->pos);
00220             map.set_starting_position(i->side,i->pos);
00221             LOG_NG << "placing side " << i->side << " at " << i->pos << '\n';
00222         }
00223     }
00224 }
00225 
00226 void play_controller::objectives(){
00227     menu_handler_.objectives(player_number_);
00228 }
00229 
00230 void play_controller::show_statistics(){
00231     menu_handler_.show_statistics(gui_->viewing_team()+1);
00232 }
00233 
00234 void play_controller::unit_list(){
00235     menu_handler_.unit_list();
00236 }
00237 
00238 void play_controller::status_table(){
00239     menu_handler_.status_table();
00240 }
00241 
00242 void play_controller::save_game(){
00243     menu_handler_.save_game("",gui::OK_CANCEL);
00244 }
00245 
00246 void play_controller::save_replay(){
00247     menu_handler_.save_game("", gui::OK_CANCEL, false, true);
00248 }
00249 
00250 void play_controller::save_map(){
00251     menu_handler_.save_map();
00252 }
00253 
00254 void play_controller::load_game(){
00255     menu_handler_.load_game();
00256 }
00257 
00258 void play_controller::preferences(){
00259     menu_handler_.preferences();
00260 }
00261 
00262 void play_controller::cycle_units(){
00263     mouse_handler_.cycle_units(browse_);
00264 }
00265 
00266 void play_controller::cycle_back_units(){
00267     mouse_handler_.cycle_back_units(browse_);
00268 }
00269 
00270 void play_controller::show_chat_log(){
00271     menu_handler_.show_chat_log();
00272 }
00273 
00274 void play_controller::show_help(){
00275     menu_handler_.show_help();
00276 }
00277 
00278 void play_controller::undo(){
00279     // deselect unit (only here, not to be done when undoing attack-move)
00280     mouse_handler_.deselect_hex();
00281     menu_handler_.undo(player_number_);
00282 }
00283 
00284 void play_controller::redo(){
00285     // deselect unit (only here, not to be done when undoing attack-move)
00286     mouse_handler_.deselect_hex();
00287     menu_handler_.redo(player_number_);
00288 }
00289 
00290 void play_controller::show_enemy_moves(bool ignore_units){
00291     menu_handler_.show_enemy_moves(ignore_units, player_number_);
00292 }
00293 
00294 void play_controller::goto_leader(){
00295     menu_handler_.goto_leader(player_number_);
00296 }
00297 
00298 void play_controller::unit_description(){
00299     menu_handler_.unit_description(mouse_handler_);
00300 }
00301 
00302 void play_controller::toggle_grid(){
00303     menu_handler_.toggle_grid();
00304 }
00305 
00306 void play_controller::search(){
00307     menu_handler_.search();
00308 }
00309 
00310 int play_controller::get_ticks(){
00311     return ticks_;
00312 }
00313 
00314 void play_controller::fire_prestart(bool execute){
00315     // pre-start events must be executed before any GUI operation,
00316     // as those may cause the display to be refreshed.
00317     if (execute){
00318         update_locker lock_display(gui_->video());
00319         game_events::fire("prestart");
00320     }
00321 }
00322 
00323 void play_controller::fire_start(bool execute){
00324     if(execute) {
00325         game_events::fire("start");
00326         gamestate_.set_variable("turn_number", "1");
00327         first_turn_ = true;
00328     } else {
00329         first_turn_ = false;
00330     }
00331 }
00332 
00333 void play_controller::init_gui(){
00334     gui_->begin_game();
00335     gui_->adjust_colours(0,0,0);
00336 
00337     for(std::vector<team>::iterator t = teams_.begin(); t != teams_.end(); ++t) {
00338 		::clear_shroud(*gui_,map_,units_,teams_,(t-teams_.begin()));
00339     }
00340 }
00341 
00342 void play_controller::init_side(const unsigned int team_index, bool /*is_replay*/){
00343     log_scope("player turn");
00344     team& current_team = teams_[team_index];
00345 
00346     mouse_handler_.set_team(team_index+1);
00347 
00348     // If we are observers we move to watch next team if it is allowed
00349     if (team_manager_.is_observer()
00350         && !current_team.get_disallow_observers()) {
00351         gui_->set_team(size_t(team_index));
00352     }
00353         gui_->set_playing_team(size_t(team_index));
00354 
00355     std::stringstream player_number_str;
00356     player_number_str << player_number_;
00357     gamestate_.set_variable("side_number",player_number_str.str());
00358     gamestate_.last_selected = gamemap::location::null_location;
00359 
00360     /*
00361         Normally, events must not be actively fired through replays, because
00362         they have been recorded previously and therefore will get executed anyway.
00363         Firing them in the normal code would lead to double execution.
00364         However, the following events are different in that they need to be executed _now_
00365         (before calculation of income and healing) or we will risk OOS errors if we manipulate
00366         these informations inside the events and in the replay have a different order of execution.
00367     */
00368     bool real_side_change = true;
00369     if(first_turn_) {
00370         game_events::fire("turn 1");
00371         game_events::fire("new turn");
00372         game_events::fire("side turn");
00373         first_turn_ = false;
00374     } else if (team_index != (first_player_ - 1) || status_.turn() > start_turn_) {
00375         // Fire side turn event only if real side change occurs,
00376         // not counting changes from void to a side
00377         game_events::fire("side turn");
00378     } else {
00379         real_side_change = false;
00380     }
00381 
00382     // We want to work out if units for this player should get healed,
00383     // and the player should get income now.
00384     // Healing/income happen if it's not the first turn of processing,
00385     // or if we are loading a game, and this is not the player it started with.
00386     const bool turn_refresh = status_.turn() > start_turn_ || (loading_game_ && team_index != (first_player_ - 1));
00387 
00388     if(turn_refresh) {
00389         for(unit_map::iterator i = units_.begin(); i != units_.end(); ++i) {
00390             if(i->second.side() == static_cast<size_t>(player_number_)) {
00391                 i->second.new_turn();
00392             }
00393         }
00394 
00395         current_team.new_turn();
00396 
00397         // If the expense is less than the number of villages owned,
00398         // then we don't have to pay anything at all
00399         const int expense = team_upkeep(units_,player_number_) -
00400                                 current_team.villages().size();
00401         if(expense > 0) {
00402             current_team.spend_gold(expense);
00403         }
00404 
00405         calculate_healing((*gui_),map_,units_,player_number_,teams_, !skip_replay_);
00406         reset_resting(units_, player_number_);
00407     }
00408     if(turn_refresh || real_side_change) {
00409         game_events::fire("turn refresh");
00410     }
00411 
00412     const time_of_day &tod = status_.get_time_of_day();
00413     current_team.set_time_of_day(int(status_.turn()), tod);
00414 
00415     if(team_index == first_player_ - 1)
00416         sound::play_sound(tod.sounds, sound::SOUND_SOURCES);
00417 
00418     if (!recorder.is_skipping()){
00419 		::clear_shroud(*gui_,map_,units_,teams_,team_index);
00420         gui_->invalidate_all();
00421     }
00422 
00423     if (!recorder.is_skipping() && !skip_replay_){
00424         gui_->scroll_to_leader(units_, player_number_);
00425     }
00426 }
00427 
00428 void play_controller::finish_side_turn(){
00429     for(unit_map::iterator uit = units_.begin(); uit != units_.end(); ++uit) {
00430         if(uit->second.side() == player_number_)
00431             uit->second.end_turn();
00432     }
00433 
00434     // This implements "delayed map sharing."
00435     // It is meant as an alternative to shared vision.
00436     if(current_team().copy_ally_shroud()) {
00437         gui_->recalculate_minimap();
00438         gui_->invalidate_all();
00439     }
00440 
00441     mouse_handler_.deselect_hex();
00442     game_events::pump();
00443 }
00444 
00445 void play_controller::finish_turn(){
00446     std::stringstream event_stream;
00447     event_stream << status_.turn();
00448 
00449     {
00450         LOG_NG << "turn event..." << (recorder.is_skipping() ? "skipping" : "no skip") << "\n";
00451         update_locker lock_display(gui_->video(),recorder.is_skipping());
00452         const std::string turn_num = event_stream.str();
00453         gamestate_.set_variable("turn_number",turn_num);
00454         game_events::fire("turn " + turn_num);
00455         game_events::fire("new turn");
00456     }
00457 }
00458 
00459 bool play_controller::enemies_visible() const
00460 {
00461     // If we aren't using fog/shroud, this is easy :)
00462     if(current_team().uses_fog() == false && current_team().uses_shroud() == false)
00463         return true;
00464 
00465     // See if any enemies are visible
00466     for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u)
00467         if(current_team().is_enemy(u->second.side()) && !gui_->fogged(u->first))
00468             return true;
00469 
00470     return false;
00471 }
00472 
00473 bool play_controller::execute_command(hotkey::HOTKEY_COMMAND command, int index)
00474 {
00475     if(index >= 0) {
00476         unsigned i = static_cast<unsigned>(index);
00477         if(i < savenames_.size() && !savenames_[i].empty()) {
00478             // Load the game by throwing load_game_exception
00479             throw game::load_game_exception(savenames_[i],false,false);
00480 
00481         } else if (i < wml_commands_.size() && wml_commands_[i] != NULL) {
00482             if(gamestate_.last_selected.valid() && wml_commands_[i]->needs_select) {
00483                 recorder.add_event("select", gamestate_.last_selected);
00484             }
00485             gamemap::location const& menu_hex = mouse_handler_.get_last_hex();
00486             recorder.add_event(wml_commands_[i]->name, menu_hex);
00487             if(game_events::fire(wml_commands_[i]->name, menu_hex)) {
00488                 // The event has mutated the gamestate
00489                 apply_shroud_changes(undo_stack_, gui_, map_,
00490                     units_, teams_, (player_number_ - 1));
00491                 undo_stack_.clear();
00492             }
00493             return true;
00494         }
00495     }
00496     return command_executor::execute_command(command, index);
00497 }
00498 
00499 //! Check if a command can be executed.
00500 bool play_controller::can_execute_command(hotkey::HOTKEY_COMMAND command, int index) const
00501 {
00502     if(index >= 0) {
00503         unsigned i = static_cast<unsigned>(index);
00504         if((i < savenames_.size() && !savenames_[i].empty())
00505         || (i < wml_commands_.size() && wml_commands_[i] != NULL)) {
00506             return true;
00507         }
00508     }
00509     switch(command) {
00510 
00511     // Commands we can always do:
00512     case hotkey::HOTKEY_LEADER:
00513     case hotkey::HOTKEY_CYCLE_UNITS:
00514     case hotkey::HOTKEY_CYCLE_BACK_UNITS:
00515     case hotkey::HOTKEY_ZOOM_IN:
00516     case hotkey::HOTKEY_ZOOM_OUT:
00517     case hotkey::HOTKEY_ZOOM_DEFAULT:
00518     case hotkey::HOTKEY_FULLSCREEN:
00519     case hotkey::HOTKEY_SCREENSHOT:
00520     case hotkey::HOTKEY_MAP_SCREENSHOT:
00521     case hotkey::HOTKEY_ACCELERATED:
00522     case hotkey::HOTKEY_SAVE_MAP:
00523     case hotkey::HOTKEY_TOGGLE_GRID:
00524     case hotkey::HOTKEY_MOUSE_SCROLL:
00525     case hotkey::HOTKEY_STATUS_TABLE:
00526     case hotkey::HOTKEY_MUTE:
00527     case hotkey::HOTKEY_PREFERENCES:
00528     case hotkey::HOTKEY_OBJECTIVES:
00529     case hotkey::HOTKEY_UNIT_LIST:
00530     case hotkey::HOTKEY_STATISTICS:
00531     case hotkey::HOTKEY_QUIT_GAME:
00532     case hotkey::HOTKEY_SEARCH:
00533     case hotkey::HOTKEY_HELP:
00534     case hotkey::HOTKEY_USER_CMD:
00535     case hotkey::HOTKEY_CUSTOM_CMD:
00536     case hotkey::HOTKEY_AI_FORMULA:
00537     case hotkey::HOTKEY_CLEAR_MSG:
00538 #ifdef USRCMD2
00539 //%%
00540     case hotkey::HOTKEY_USER_CMD_2:
00541     case hotkey::HOTKEY_USER_CMD_3:
00542 #endif
00543         return true;
00544 
00545     // Commands that have some preconditions:
00546     case hotkey::HOTKEY_SAVE_GAME:
00547     case hotkey::HOTKEY_SAVE_REPLAY:
00548         return !events::commands_disabled;
00549 
00550     case hotkey::HOTKEY_SHOW_ENEMY_MOVES:
00551     case hotkey::HOTKEY_BEST_ENEMY_MOVES:
00552         return !linger_ && enemies_visible();
00553 
00554     case hotkey::HOTKEY_LOAD_GAME:
00555         return network::nconnections() == 0; // Can only load games if not in a network game
00556 
00557     case hotkey::HOTKEY_CHAT_LOG:
00558         return network::nconnections() > 0;
00559 
00560     case hotkey::HOTKEY_REDO:
00561         return !linger_ && !redo_stack_.empty() && !events::commands_disabled;
00562     case hotkey::HOTKEY_UNDO:
00563         return !linger_ && !undo_stack_.empty() && !events::commands_disabled;
00564 
00565     case hotkey::HOTKEY_UNIT_DESCRIPTION:
00566         return menu_handler_.current_unit(mouse_handler_) != units_.end();
00567 
00568     case hotkey::HOTKEY_RENAME_UNIT:
00569         return !events::commands_disabled &&
00570             menu_handler_.current_unit(mouse_handler_) != units_.end() &&
00571             !(menu_handler_.current_unit(mouse_handler_)->second.unrenamable()) &&
00572             menu_handler_.current_unit(mouse_handler_)->second.side() == gui_->viewing_team()+1 &&
00573             teams_[menu_handler_.current_unit(mouse_handler_)->second.side() - 1].is_human();
00574 
00575     default:
00576         return false;
00577     }
00578 }
00579 
00580 void play_controller::enter_textbox()
00581 {
00582     if(menu_handler_.get_textbox().active() == false) {
00583         return;
00584     }
00585 
00586     const std::string str = menu_handler_.get_textbox().box()->text();
00587     const unsigned int team_num = player_number_;
00588     events::mouse_handler& mousehandler = mouse_handler_;
00589 
00590     switch(menu_handler_.get_textbox().mode()) {
00591     case gui::TEXTBOX_SEARCH:
00592         menu_handler_.do_search(str);
00593         menu_handler_.get_textbox().close(*gui_);
00594         break;
00595     case gui::TEXTBOX_MESSAGE:
00596         menu_handler_.do_speak();
00597         menu_handler_.get_textbox().close(*gui_);  //need to close that one after executing do_speak() !
00598         break;
00599     case gui::TEXTBOX_COMMAND:
00600         menu_handler_.get_textbox().close(*gui_);
00601         menu_handler_.do_command(str, team_num, mousehandler);
00602         break;
00603     case gui::TEXTBOX_AI:
00604         menu_handler_.get_textbox().close(*gui_);
00605         menu_handler_.do_ai_formula(str, team_num, mousehandler);
00606         break;
00607     default:
00608         menu_handler_.get_textbox().close(*gui_);
00609         LOG_STREAM(err, display) << "unknown textbox mode\n";
00610     }
00611 
00612 }
00613 
00614 team& play_controller::current_team()
00615 {
00616     assert(player_number_ > 0 && player_number_ <= teams_.size());
00617     return teams_[player_number_-1];
00618 }
00619 
00620 const team& play_controller::current_team() const
00621 {
00622     assert(player_number_ > 0 && player_number_ <= teams_.size());
00623     return teams_[player_number_-1];
00624 }
00625 
00626 //! Find a human team (ie one we own) starting backwards from 'team_num'.
00627 int play_controller::find_human_team_before(const size_t team_num) const
00628 {
00629     if (team_num > teams_.size())
00630         return -2;
00631 
00632     int human_side = -2;
00633     for (int i = team_num-2; i > -1; --i) {
00634         if (teams_[i].is_human()) {
00635             human_side = i;
00636             break;
00637         }
00638     }
00639     if (human_side == -2) {
00640         for (size_t i = teams_.size()-1; i > team_num-1; --i) {
00641             if (teams_[i].is_human()) {
00642                 human_side = i;
00643                 break;
00644             }
00645         }
00646     }
00647     return human_side+1;
00648 }
00649 
00650 //! Process mouse- and keypress-events from SDL.
00651 void play_controller::handle_event(const SDL_Event& event)
00652 {
00653     if(gui::in_dialog()) {
00654         return;
00655     }
00656 
00657     switch(event.type) {
00658     case SDL_KEYDOWN:
00659 //%%
00660         // Detect key press events, unless there is a textbox present on-screen,
00661         // in which case the key press events should go only to it.
00662         if(menu_handler_.get_textbox().active() == false) {
00663             hotkey::key_event(*gui_,event.key,this);
00664         } else {
00665             if(event.key.keysym.sym == SDLK_ESCAPE) {
00666                 menu_handler_.get_textbox().close(*gui_);
00667             } else if(event.key.keysym.sym == SDLK_TAB) {
00668                 menu_handler_.get_textbox().tab(teams_, units_, *gui_);
00669             } else if(event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER) {
00670                 enter_textbox();
00671             }
00672             break;
00673         }
00674 
00675         // intentionally fall-through
00676     case SDL_KEYUP:
00677 
00678         // If the user has pressed 1 through 9, we want to show
00679         // how far the unit can move in that many turns
00680         if(event.key.keysym.sym >= '1' && event.key.keysym.sym <= '7') {
00681             const int new_path_turns = (event.type == SDL_KEYDOWN) ?
00682                                        event.key.keysym.sym - '1' : 0;
00683 
00684             if(new_path_turns != mouse_handler_.get_path_turns()) {
00685                 mouse_handler_.set_path_turns(new_path_turns);
00686 
00687                 const unit_map::iterator u = mouse_handler_.selected_unit();
00688 
00689                 if(u != units_.end()) {
00690                     const bool teleport = u->second.get_ability_bool("teleport",u->first);
00691                     mouse_handler_.set_current_paths(paths(map_,units_,u->first,
00692                                            teams_,false,teleport, teams_[gui_->viewing_team()],
00693                                            mouse_handler_.get_path_turns()));
00694                     gui_->highlight_reach(mouse_handler_.get_current_paths());
00695                 }
00696             }
00697         }
00698 //%%
00699 //      std::cerr << "@play_controller.cpp::handle_event : Key pressed: " << event.key.keysym.sym
00700 //              << std::endl;
00701 
00702         break;
00703     case SDL_MOUSEMOTION:
00704         // Ignore old mouse motion events in the event queue
00705         SDL_Event new_event;
00706 
00707         if(SDL_PeepEvents(&new_event,1,SDL_GETEVENT,
00708                     SDL_EVENTMASK(SDL_MOUSEMOTION)) > 0) {
00709             while(SDL_PeepEvents(&new_event,1,SDL_GETEVENT,
00710                         SDL_EVENTMASK(SDL_MOUSEMOTION)) > 0) {};
00711             mouse_handler_.mouse_motion(new_event.motion, browse_);
00712         } else {
00713             mouse_handler_.mouse_motion(event.motion, browse_);
00714         }
00715         break;
00716     case SDL_MOUSEBUTTONDOWN:
00717     case SDL_MOUSEBUTTONUP:
00718         mouse_handler_.mouse_press(event.button, browse_);
00719         if (mouse_handler_.get_undo()){
00720             mouse_handler_.set_undo(false);
00721             menu_handler_.undo(player_number_);
00722         }
00723         if (mouse_handler_.get_show_menu()){
00724             show_menu(gui_->get_theme().context_menu()->items(),event.button.x,event.button.y,true);
00725         }
00726         break;
00727     default:
00728         break;
00729     }
00730 }
00731 
00732 void play_controller::play_slice()
00733 {
00734     CKey key;
00735 
00736     events::pump();
00737     events::raise_process_event();
00738 
00739     events::raise_draw_event();
00740     soundsources_manager_->update();
00741 
00742     const theme::menu* const m = gui_->menu_pressed();
00743     if(m != NULL) {
00744         const SDL_Rect& menu_loc = m->location(gui_->screen_area());
00745         show_menu(m->items(),menu_loc.x+1,menu_loc.y + menu_loc.h + 1,false);
00746         return;
00747     }
00748 
00749     int mousex, mousey;
00750     bool middle_pressed = (SDL_GetMouseState(&mousex,&mousey)& SDL_BUTTON(2)) != 0;
00751     tooltips::process(mousex, mousey);
00752 
00753     const int scroll_threshold = (preferences::mouse_scroll_enabled()) ? 5 : 0;
00754     bool was_scrolling = scrolling_;
00755     scrolling_ = false;
00756 
00757     if((key[SDLK_UP] && !menu_handler_.get_textbox().active()) || mousey < scroll_threshold) {
00758         gui_->scroll(0,-preferences::scroll_speed());
00759         scrolling_ = true;
00760     }
00761 
00762     if((key[SDLK_DOWN] && !menu_handler_.get_textbox().active()) || mousey > gui_->h()-scroll_threshold) {
00763         gui_->scroll(0,preferences::scroll_speed());
00764         scrolling_ = true;
00765     }
00766 
00767     if((key[SDLK_LEFT] && !menu_handler_.get_textbox().active()) || mousex < scroll_threshold) {
00768         gui_->scroll(-preferences::scroll_speed(),0);
00769         scrolling_ = true;
00770     }
00771 
00772     if((key[SDLK_RIGHT] && !menu_handler_.get_textbox().active()) || mousex > gui_->w()-scroll_threshold) {
00773         gui_->scroll(preferences::scroll_speed(),0);
00774         scrolling_ = true;
00775     }
00776 
00777     if (middle_pressed) {
00778         const SDL_Rect& rect = gui_->map_outside_area();
00779         if (point_in_rect(mousex, mousey,rect)) {
00780             // relative distance from the center to the border
00781             // NOTE: the view is a rectangle, so can be more sensible in one direction
00782             // but seems intuitive to use and it's useful since you must
00783             // more often scroll in the direction where the view is shorter
00784             const double xdisp = ((1.0*mousex / rect.w) - 0.5);
00785             const double ydisp = ((1.0*mousey / rect.h) - 0.5);
00786 
00787             // 4.0 give twice the normal speed when mouse is at border (xdisp=0.5)
00788             const double scroll_speed = 4.0 * preferences::scroll_speed();
00789 
00790             const int xspeed = round_double(xdisp * scroll_speed);
00791             const int yspeed = round_double(ydisp * scroll_speed);
00792 
00793             gui_->scroll(xspeed,yspeed);
00794             scrolling_ = true;
00795         }
00796     }
00797 
00798     gui_->draw();
00799     if (!scrolling_) {
00800         if (was_scrolling) {
00801             // scrolling ended, update the cursor and the brightened hex
00802             mouse_handler_.mouse_update(browse_);
00803         }
00804         gui_->delay(20);
00805     }
00806 
00807     if(!browse_ && current_team().objectives_changed()) {
00808         dialogs::show_objectives(*gui_, level_, current_team().objectives());
00809         current_team().reset_objectives_changed();
00810     }
00811 }
00812 
00813 static void trim_items(std::vector<std::string>& newitems) {
00814     if (newitems.size() > 5) {
00815         std::vector<std::string> subitems;
00816         subitems.push_back(newitems[0]);
00817         subitems.push_back(newitems[1]);
00818         subitems.push_back(newitems[newitems.size() / 3]);
00819         subitems.push_back(newitems[newitems.size() * 2 / 3]);
00820         subitems.push_back(newitems.back());
00821         newitems = subitems;
00822     }
00823 }
00824 
00825 void play_controller::expand_autosaves(std::vector<std::string>& items)
00826 {
00827     savenames_.clear();
00828     for (unsigned int i = 0; i < items.size(); ++i) {
00829         if (items[i] == "AUTOSAVES") {
00830             items.erase(items.begin() + i);
00831             std::vector<std::string> newitems;
00832             std::vector<std::string> newsaves;
00833             for (unsigned int turn = status_.turn(); turn != 0; turn--) {
00834                 std::string name = gamestate_.label + "-" + _("Auto-Save") + lexical_cast<std::string>(turn);
00835                 if (save_game_exists(name)) {
00836                     if(preferences::compress_saves()) {
00837                         newsaves.push_back(name + ".gz");
00838                     } else {
00839                         newsaves.push_back(name);
00840                     }
00841                     if (turn == 1) {
00842                         newitems.push_back(_("Back to start"));
00843                     } else {
00844                         newitems.push_back(_("Back to turn ") + lexical_cast<std::string>(turn));
00845                     }
00846                 }
00847             }
00848 
00849             // Make sure list doesn't get too long: keep top two,
00850             // midpoint and bottom.
00851             trim_items(newitems);
00852             trim_items(newsaves);
00853 
00854             items.insert(items.begin()+i, newitems.begin(), newitems.end());
00855             savenames_.insert(savenames_.end(), newsaves.begin(), newsaves.end());
00856             break;
00857         }
00858         savenames_.push_back("");
00859     }
00860 }
00861 
00862 void play_controller::expand_wml_commands(std::vector<std::string>& items)
00863 {
00864     wml_commands_.clear();
00865     for (unsigned int i = 0; i < items.size(); ++i) {
00866         if (items[i] == "wml") {
00867             items.erase(items.begin() + i);
00868             std::map<std::string, wml_menu_item*>& gs_wmi = gamestate_.wml_menu_items;
00869             if(gs_wmi.empty())
00870                 break;
00871             std::vector<std::string> newitems;
00872 
00873             char buf[50];
00874             const gamemap::location& hex = mouse_handler_.get_last_hex();
00875             snprintf(buf,sizeof(buf),"%d",hex.x+1);
00876             gamestate_.set_variable("x1", buf);
00877             snprintf(buf,sizeof(buf),"%d",hex.y+1);
00878             gamestate_.set_variable("y1", buf);
00879             scoped_xy_unit highlighted_unit("unit", hex.x, hex.y, units_);
00880 
00881             std::map<std::string, wml_menu_item*>::iterator itor;
00882             for (itor = gs_wmi.begin(); itor != gs_wmi.end()
00883                 && newitems.size() < MAX_WML_COMMANDS; ++itor) {
00884                 config& show_if = itor->second->show_if;
00885                 config filter_location = itor->second->filter_location;
00886                 if ((show_if.empty()
00887                     || game_events::conditional_passed(&units_, &show_if))
00888                 && (filter_location.empty()
00889                     || terrain_filter(&filter_location, map_, status_, units_)(hex))
00890                 && (!itor->second->needs_select
00891                     || gamestate_.last_selected.valid()))
00892                 {
00893                     wml_commands_.push_back(itor->second);
00894                     std::string newitem = itor->second->description;
00895 
00896                     // Prevent accidental hotkey binding by appending a space
00897                     push_back<std::string, char>(newitem, ' ');
00898                     newitems.push_back(newitem);
00899                 }
00900             }
00901             items.insert(items.begin()+i, newitems.begin(), newitems.end());
00902             break;
00903         }
00904         wml_commands_.push_back(NULL);
00905     }
00906 }
00907 
00908 void play_controller::show_menu(const std::vector<std::string>& items_arg, int xloc, int yloc, bool context_menu)
00909 {
00910     std::vector<std::string> items = items_arg;
00911     hotkey::HOTKEY_COMMAND command;
00912     std::vector<std::string>::iterator i = items.begin();
00913     while(i != items.end()) {
00914         if (*i == "AUTOSAVES") {
00915             // Autosave visibility is similar to LOAD_GAME hotkey
00916             command = hotkey::HOTKEY_LOAD_GAME;
00917         } else {
00918             command = hotkey::get_hotkey(*i).get_id();
00919         }
00920         // Remove WML commands if they would not be allowed here
00921         if(*i == "wml") {
00922             if(!context_menu || gui_->viewing_team() != gui_->playing_team()
00923             || events::commands_disabled || !teams_[gui_->viewing_team()].is_human()) {
00924                 i = items.erase(i);
00925                 continue;
00926             }
00927         // Remove commands that can't be executed or don't belong in this type of menu
00928         } else if(!can_execute_command(command)
00929         || (context_menu && !in_context_menu(command))) {
00930             i = items.erase(i);
00931             continue;
00932         }
00933         ++i;
00934     }
00935 
00936     // Add special non-hotkey items to the menu and remember their indeces
00937     expand_autosaves(items);
00938     expand_wml_commands(items);
00939 
00940     if(items.empty())
00941         return;
00942 
00943     command_executor::show_menu(items, xloc, yloc, context_menu, *gui_);
00944 }
00945 
00946 //! Determines whether the command should be in the context menu or not.
00947 //! Independant of whether or not we can actually execute the command.
00948 bool play_controller::in_context_menu(hotkey::HOTKEY_COMMAND command) const
00949 {
00950     switch(command) {
00951     // Only display these if the mouse is over a castle or keep tile
00952     case hotkey::HOTKEY_RECRUIT:
00953     case hotkey::HOTKEY_REPEAT_RECRUIT:
00954     case hotkey::HOTKEY_RECALL: {
00955         // last_hex_ is set by mouse_events::mouse_motion
00956         // Enable recruit/recall on castle/keep tiles
00957         const unit_map::const_iterator leader = team_leader(player_number_,units_);
00958         if (leader != units_.end()) {
00959             return can_recruit_on(map_, leader->first, mouse_handler_.get_last_hex());
00960         } else {
00961             return false;
00962         }
00963     }
00964     default:
00965         return true;
00966     }
00967 }
00968 
00969 std::string play_controller::get_action_image(hotkey::HOTKEY_COMMAND command, int index) const
00970 {
00971     if(index >= 0 && index < static_cast<int>(wml_commands_.size())) {
00972         wml_menu_item* const& wmi = wml_commands_[index];
00973         if(wmi != NULL) {
00974             return wmi->image.empty() ? game_config::wml_menu_image : wmi->image;
00975         }
00976     }
00977     return command_executor::get_action_image(command, index);
00978 }
00979 
00980 hotkey::ACTION_STATE play_controller::get_action_state(hotkey::HOTKEY_COMMAND command) const
00981 {
00982     switch(command) {
00983     case hotkey::HOTKEY_DELAY_SHROUD:
00984         return current_team().auto_shroud_updates() ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
00985     default:
00986         return hotkey::ACTION_STATELESS;
00987     }
00988 }
00989 

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