/* $Id$ */ /* Copyright (C) 2006 - 2009 by Joerg Hinrichs wesnoth playturn Copyright (C) 2003 by David White Part of the Battle for Wesnoth Project http://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 or at your option any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the COPYING file for more details. */ /** * @file menu_events.cpp * Operations activated from menus/hotkeys while playing a game. * E.g. Unitlist, status_table, save_game, save_map, chat, show_help, etc. */ #include "global.hpp" #include "ai/manager.hpp" #include "dialogs.hpp" #include "formatter.hpp" #include "filechooser.hpp" #include "foreach.hpp" #include "game_end_exceptions.hpp" #include "game_events.hpp" #include "gettext.hpp" #include "gui/dialogs/transient_message.hpp" #include "gui/widgets/window.hpp" #include "help.hpp" #include "log.hpp" #include "map.hpp" #include "map_label.hpp" #include "marked-up_text.hpp" #include "menu_events.hpp" #include "mouse_events.hpp" #include "preferences_display.hpp" #include "replay.hpp" #include "resources.hpp" #include "savegame.hpp" #include "sound.hpp" #include "statistics_dialog.hpp" #include "unit_display.hpp" #include "wml_separators.hpp" #include "formula_string_utils.hpp" #include "scripting/lua.hpp" #include "widgets/combo.hpp" static lg::log_domain log_engine("engine"); #define ERR_NG LOG_STREAM(err, log_engine) #define LOG_NG LOG_STREAM(info, log_engine) namespace events{ class delete_recall_unit : public gui::dialog_button_action { public: delete_recall_unit(game_display& disp, gui::filter_textbox& filter, std::vector& units, undo_list& undo_stack, undo_list& redo_stack) : disp_(disp), filter_(filter), units_(units), undo_stack_(undo_stack), redo_stack_(redo_stack) {} private: gui::dialog_button_action::RESULT button_pressed(int menu_selection); game_display& disp_; gui::filter_textbox& filter_; std::vector& units_; undo_list& undo_stack_; undo_list& redo_stack_; }; gui::dialog_button_action::RESULT delete_recall_unit::button_pressed(int menu_selection) { const size_t index = size_t(filter_.get_index(menu_selection)); if(index < units_.size()) { const unit& u = units_[index]; //If the unit is of level > 1, or is close to advancing, //we warn the player about it std::stringstream message; if (u.loyal()) { message << _("My lord, this unit is loyal and requires no upkeep! ") << (u.gender() == unit_race::MALE ? _("Do you really want to dismiss him?") : _("Do you really want to dismiss her?")); } else if(u.level() > 1) { message << _("My lord, this unit is an experienced one, having advanced levels! ") << (u.gender() == unit_race::MALE ? _("Do you really want to dismiss him?") : _("Do you really want to dismiss her?")); } else if(u.experience() > u.max_experience()/2) { message << _("My lord, this unit is close to advancing a level! ") << (u.gender() == unit_race::MALE ? _("Do you really want to dismiss him?") : _("Do you really want to dismiss her?")); } if(!message.str().empty()) { const int res = gui::dialog(disp_,"",message.str(),gui::YES_NO).show(); if(res != 0) { return gui::CONTINUE_DIALOG; } } // Remove the item from filter_textbox memory filter_.delete_item(menu_selection); //add dismissal to the undo stack undo_stack_.push_back(undo_action(u, map_location(), static_cast(index), true)); units_.erase(units_.begin() + index); recorder.add_disband(index); //clear the redo stack to avoid duplication of dismissals redo_stack_.clear(); return gui::DELETE_ITEM; } else { return gui::CONTINUE_DIALOG; } } menu_handler::menu_handler(game_display* gui, unit_map& units, std::vector& teams, const config& level, const gamemap& map, const config& game_config, const tod_manager& tod_mng, game_state& gamestate, undo_list& undo_stack, undo_list& redo_stack) : gui_(gui), units_(units), teams_(teams), level_(level), map_(map), game_config_(game_config), tod_manager_(tod_mng), gamestate_(gamestate), undo_stack_(undo_stack), redo_stack_(redo_stack), textbox_info_(), last_search_(), last_search_hit_(), last_recruit_() { } menu_handler::~menu_handler() { } const undo_list& menu_handler::get_undo_list() const{ return undo_stack_; } gui::floating_textbox& menu_handler::get_textbox(){ return textbox_info_; } std::string menu_handler::get_title_suffix(int side_num) { int controlled_recruiters = 0; for(size_t i = 0; i < teams_.size(); ++i) { if(teams_[i].is_human() && !teams_[i].recruits().empty() && units_.find_leader(i + 1) != units_.end()) { ++controlled_recruiters; } } std::stringstream msg; if(controlled_recruiters >= 2) { unit_map::const_iterator leader = units_.find_leader(side_num); if(leader != units_.end() && !leader->second.name().empty()) { msg << " (" << leader->second.name(); msg << ")"; } } return msg.str(); } void menu_handler::objectives(int side_num) { config cfg; cfg["side"] = str_cast(side_num); game_events::handle_event_command("show_objectives", game_events::queued_event("_from_interface", map_location(), map_location(), config()), vconfig(cfg)); team ¤t_team = teams_[side_num - 1]; dialogs::show_objectives(*gui_, level_, current_team.objectives()); current_team.reset_objectives_changed(); } void menu_handler::show_statistics(int side_num) { team ¤t_team = teams_[side_num - 1]; // Current Player name const std::string &player = current_team.current_player(); //add player's name to title of dialog std::stringstream title_str; title_str << _("Statistics") << " (" << player << ")"; statistics_dialog stats_dialog(*gui_, title_str.str(), side_num, current_team.save_id(), player); stats_dialog.show(); } void menu_handler::unit_list() { const std::string heading = std::string(1,HEADING_PREFIX) + _("Type") + COLUMN_SEPARATOR + _("Name") + COLUMN_SEPARATOR + _("Level^Lvl.") + COLUMN_SEPARATOR + _("HP") + COLUMN_SEPARATOR + _("XP") + COLUMN_SEPARATOR + _("unit list^Traits") + COLUMN_SEPARATOR + _("Moves") + COLUMN_SEPARATOR + _("Location^Loc.") + COLUMN_SEPARATOR + _("Status"); gui::menu::basic_sorter sorter; sorter.set_alpha_sort(0).set_alpha_sort(1).set_numeric_sort(2).set_numeric_sort(3) .set_xp_sort(4,2).set_alpha_sort(5).set_numeric_sort(6); std::vector items; items.push_back(heading); std::vector locations_list; std::vector units_list; int selected = 0; for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) { if (i->second.side() != gui_->viewing_side()) continue; std::stringstream row; // If a unit is already selected on the map, we do the same in the unit list dialog if (gui_->selected_hex() == i->first) { row << DEFAULT_ITEM; selected = units_list.size(); } //%% // If unit is leader, show name in special color, e.g. gold/silver /** @todo TODO: hero just has overlay "misc/hero-icon.png" - needs an ability to query */ if(i->second.can_recruit() ) { // row << "<255,255,200>"; row << "<205,173,0>"; // gold3 } row << i->second.type_name() << COLUMN_SEPARATOR; if(i->second.can_recruit() ) { // row << "<255,255,200>"; row << "<205,173,0>"; // gold3 } row << i->second.name() << COLUMN_SEPARATOR; // Show units of level (0=gray, 1 normal, 2 bold, 2+ bold&wbright) const int level = i->second.level(); if(level < 1) { row << "<150,150,150>"; } else if(level == 1) { row << font::NORMAL_TEXT; } else if(level == 2) { row << font::BOLD_TEXT; } if(i->second.level() > 2 ) { row << font::BOLD_TEXT << "<255,255,255>"; } row << level << COLUMN_SEPARATOR; // Display HP // see also unit_preview_pane in dialogs.cpp row << font::color2markup(i->second.hp_color()); row << i->second.hitpoints() << "/" << i->second.max_hitpoints() << COLUMN_SEPARATOR; // Display XP row << font::color2markup(i->second.xp_color()); row << i->second.experience() << "/"; if(i->second.can_advance()) { row << i->second.max_experience(); } else { row << "-"; } row << COLUMN_SEPARATOR; // TODO: show 'loyal' in green / xxx in red // how to handle translations ?? row << i->second.traits_description() << COLUMN_SEPARATOR; // display move left (0=red, moved=yellow, not moved=green) if(i->second.movement_left() == 0) { row << font::RED_TEXT; } else if(i->second.movement_left() < i->second.total_movement() ) { row << "<255,255,0>"; } else { row << font::GREEN_TEXT; } row << i->second.movement_left() << "/" << i->second.total_movement() << COLUMN_SEPARATOR; const int def = 100 - i->second.defense_modifier(map_.get_terrain(i->first)); int val = (game_config::defense_color_scale.size()-1) * def/100; row << rgb2highlight(game_config::defense_color_scale[val]); row << i->first << COLUMN_SEPARATOR; // show icons if unit is slowed, poisoned, petrified, invisible: if(i->second.get_state(unit::STATE_PETRIFIED)) row << IMAGE_PREFIX << "misc/petrified.png" << IMG_TEXT_SEPARATOR; if(i->second.get_state(unit::STATE_SLOWED)) row << IMAGE_PREFIX << "misc/slowed.png" << IMG_TEXT_SEPARATOR; if(i->second.get_state(unit::STATE_POISONED)) row << IMAGE_PREFIX << "misc/poisoned.png" << IMG_TEXT_SEPARATOR; /** @todo FIXME: condition for "invisible" does not work */ //if(utils::string_bool(i->second.get_state("hides"))) // "hides" gives ability, not status if(utils::string_bool(i->second.get_state("invisible"))) row << IMAGE_PREFIX << "misc/invisible.png"; //%% items.push_back(row.str()); locations_list.push_back(i->first); units_list.push_back(i->second); } { dialogs::units_list_preview_pane unit_preview(*gui_, &map_, units_list); unit_preview.set_selection(selected); gui::dialog umenu(*gui_, _("Unit List"), "", gui::NULL_DIALOG); umenu.set_menu(items, &sorter); umenu.add_pane(&unit_preview); umenu.add_button(new gui::standard_dialog_button(gui_->video(), _("Scroll To"), 0, false), gui::dialog::BUTTON_STANDARD); umenu.add_button(new gui::standard_dialog_button(gui_->video(), _("Close"), 1, true), gui::dialog::BUTTON_STANDARD); umenu.set_basic_behavior(gui::OK_CANCEL); selected = umenu.show(); } // this will kill the dialog before scrolling if(selected >= 0 && selected < int(locations_list.size())) { const map_location& loc = locations_list[selected]; gui_->scroll_to_tile(loc,game_display::WARP); gui_->select_hex(loc); } } namespace { class leader_scroll_dialog : public gui::dialog { public: leader_scroll_dialog(display &disp, const std::string &title, std::vector &leader_bools, int selected, gui::DIALOG_RESULT extra_result) : dialog(disp, title, "", gui::NULL_DIALOG), scroll_btn_(new gui::standard_dialog_button(disp.video(), _("Scroll To"), 0, false)), leader_bools_(leader_bools), extra_result_(extra_result) { scroll_btn_->enable(leader_bools[selected]); add_button(scroll_btn_, gui::dialog::BUTTON_STANDARD); add_button(new gui::standard_dialog_button(disp.video(), _("Close"), 1, true), gui::dialog::BUTTON_STANDARD); } void action(gui::dialog_process_info &info) { const bool leader_bool = leader_bools_[get_menu().selection()]; scroll_btn_->enable(leader_bool); if(leader_bool && (info.double_clicked || (!info.key_down && (info.key[SDLK_RETURN] || info.key[SDLK_KP_ENTER])))) { set_result(get_menu().selection()); } else if(!info.key_down && info.key[SDLK_ESCAPE]) { set_result(gui::CLOSE_DIALOG); } else if(!info.key_down && info.key[SDLK_SPACE]) { set_result(extra_result_); } else if(result() == gui::CONTINUE_DIALOG) { dialog::action(info); } } private: gui::standard_dialog_button *scroll_btn_; std::vector &leader_bools_; gui::DIALOG_RESULT extra_result_; }; } //end anonymous namespace void menu_handler::status_table(int selected) { std::stringstream heading; heading << HEADING_PREFIX << _("Leader") << COLUMN_SEPARATOR << ' ' << COLUMN_SEPARATOR << _("Team") << COLUMN_SEPARATOR << _("Gold") << COLUMN_SEPARATOR << _("Villages") << COLUMN_SEPARATOR << _("status^Units") << COLUMN_SEPARATOR << _("Upkeep") << COLUMN_SEPARATOR << _("Income"); gui::menu::basic_sorter sorter; sorter.set_redirect_sort(0,1).set_alpha_sort(1).set_alpha_sort(2).set_numeric_sort(3) .set_numeric_sort(4).set_numeric_sort(5).set_numeric_sort(6).set_numeric_sort(7); std::vector items; std::vector leader_bools; items.push_back(heading.str()); const team& viewing_team = teams_[gui_->viewing_team()]; unsigned total_villages = 0; // a variable to check if there are any teams to show in the table bool status_table_empty = true; //if the player is under shroud or fog, they don't get //to see details about the other sides, only their own //side, allied sides and a ??? is shown to demonstrate //lack of information about the other sides But he see //all names with in colours for(size_t n = 0; n != teams_.size(); ++n) { if(teams_[n].is_empty()||teams_[n].hidden()) { continue; } status_table_empty=false; const bool known = viewing_team.knows_about_team(n, network::nconnections() > 0); const bool enemy = viewing_team.is_enemy(n+1); std::stringstream str; const team_data data = calculate_team_data(teams_[n],n+1,units_); unit_map::const_iterator leader = units_.find_leader(n + 1); std::string leader_name; //output the number of the side first, and this will //cause it to be displayed in the correct colour if(leader != units_.end()) { // Add leader image. If it's fogged // show only a random leader image. if (known || game_config::debug) { str << IMAGE_PREFIX << leader->second.absolute_image(); leader_bools.push_back(true); leader_name = leader->second.name(); } else { str << IMAGE_PREFIX << std::string("units/unknown-unit.png"); leader_bools.push_back(false); leader_name = "Unknown"; } if (gamestate_.classification().campaign_type == "multiplayer") leader_name = teams_[n].current_player(); #ifndef LOW_MEM str << "~RC(" << leader->second.team_color() << ">" << team::get_side_colour_index(n+1) << ")"; #endif } else { leader_bools.push_back(false); } str << COLUMN_SEPARATOR << team::get_side_highlight(n) << leader_name << COLUMN_SEPARATOR << (data.teamname.empty() ? teams_[n].team_name() : data.teamname) << COLUMN_SEPARATOR; if(!known && !game_config::debug) { // We don't spare more info (only name) // so let's go on next side ... items.push_back(str.str()); continue; } if(game_config::debug) { str << data.gold << COLUMN_SEPARATOR; } else if(enemy && viewing_team.uses_fog()) { str << ' ' << COLUMN_SEPARATOR; } else { str << data.gold << COLUMN_SEPARATOR; } str << data.villages << COLUMN_SEPARATOR << data.units << COLUMN_SEPARATOR << data.upkeep << COLUMN_SEPARATOR << (data.net_income < 0 ? font::BAD_TEXT : font::NULL_MARKUP) << data.net_income; total_villages += data.villages; items.push_back(str.str()); } if (total_villages > map_.villages().size()) { ERR_NG << "Logic error: map has " << map_.villages().size() << " villages but status table shows " << total_villages << " owned in total\n"; } if (status_table_empty) { // no sides to show - display empty table std::stringstream str; str << " "; for (int i=0;i<7;++i) str << COLUMN_SEPARATOR << " "; leader_bools.push_back(false); items.push_back(str.str()); } int result = 0; { leader_scroll_dialog slist(*gui_, _("Current Status"), leader_bools, selected, gui::DIALOG_FORWARD); slist.add_button(new gui::dialog_button(gui_->video(), _("More >"), gui::button::TYPE_PRESS, gui::DIALOG_FORWARD), gui::dialog::BUTTON_EXTRA_LEFT); slist.set_menu(items, &sorter); slist.get_menu().move_selection(selected); result = slist.show(); selected = slist.get_menu().selection(); } // this will kill the dialog before scrolling if (result >= 0) gui_->scroll_to_leader(units_, selected+1); else if (result == gui::DIALOG_FORWARD) scenario_settings_table(selected); } void menu_handler::scenario_settings_table(int selected) { std::stringstream heading; heading << HEADING_PREFIX << _("scenario settings^Leader") << COLUMN_SEPARATOR << COLUMN_SEPARATOR << _("scenario settings^Side") << COLUMN_SEPARATOR << _("scenario settings^Start\nGold") << COLUMN_SEPARATOR << _("scenario settings^Base\nIncome") << COLUMN_SEPARATOR << _("scenario settings^Gold Per\nVillage") << COLUMN_SEPARATOR << _("scenario settings^Fog") << COLUMN_SEPARATOR << _("scenario settings^Shroud"); gui::menu::basic_sorter sorter; sorter.set_redirect_sort(0,1).set_alpha_sort(1).set_numeric_sort(2) .set_numeric_sort(3).set_numeric_sort(4).set_numeric_sort(5) .set_alpha_sort(6).set_alpha_sort(7); std::vector items; std::vector leader_bools; items.push_back(heading.str()); const team& viewing_team = teams_[gui_->viewing_team()]; bool settings_table_empty = true; for(size_t n = 0; n != teams_.size(); ++n) { if(teams_[n].is_empty()||teams_[n].hidden()) { continue; } settings_table_empty = false; std::stringstream str; unit_map::const_iterator leader = units_.find_leader(n + 1); if(leader != units_.end()) { // Add leader image. If it's fogged // show only a random leader image. if (viewing_team.knows_about_team(n, network::nconnections() > 0) || game_config::debug) { str << IMAGE_PREFIX << leader->second.absolute_image(); leader_bools.push_back(true); } else { str << IMAGE_PREFIX << std::string("units/unknown-unit.png"); leader_bools.push_back(false); } #ifndef LOW_MEM str << "~RC(" << leader->second.team_color() << ">" << team::get_side_colour_index(n+1) << ")"; #endif } else { leader_bools.push_back(false); } str << COLUMN_SEPARATOR << team::get_side_highlight(n) << teams_[n].current_player() << COLUMN_SEPARATOR << n + 1 << COLUMN_SEPARATOR << teams_[n].start_gold() << COLUMN_SEPARATOR << teams_[n].base_income() << COLUMN_SEPARATOR << teams_[n].village_gold() << COLUMN_SEPARATOR << (teams_[n].uses_fog() ? _("yes") : _("no")) << COLUMN_SEPARATOR << (teams_[n].uses_shroud() ? _("yes") : _("no")) << COLUMN_SEPARATOR; items.push_back(str.str()); } if (settings_table_empty) { // no sides to show - display empty table std::stringstream str; for (int i=0;i<8;++i) str << " " << COLUMN_SEPARATOR; leader_bools.push_back(false); items.push_back(str.str()); } int result = 0; { leader_scroll_dialog slist(*gui_, _("Scenario Settings"), leader_bools, selected, gui::DIALOG_BACK); slist.set_menu(items, &sorter); slist.get_menu().move_selection(selected); slist.add_button(new gui::dialog_button(gui_->video(), _(" < Back"), gui::button::TYPE_PRESS, gui::DIALOG_BACK), gui::dialog::BUTTON_EXTRA_LEFT); result = slist.show(); selected = slist.get_menu().selection(); } // this will kill the dialog before scrolling if (result >= 0) gui_->scroll_to_leader(units_, selected+1); else if (result == gui::DIALOG_BACK) status_table(selected); } void menu_handler::save_map() { std::string input_name = get_dir(get_dir(get_user_data_dir() + "/editor") + "/maps/"); int res = 0; int overwrite = 1; do { res = dialogs::show_file_chooser_dialog_save(*gui_, input_name, _("Save the Map As")); if (res == 0) { if (file_exists(input_name)) { overwrite = gui::dialog(*gui_, "", _("The map already exists. Do you want to overwrite it?"), gui::YES_NO).show(); } else overwrite = 0; } } while (res == 0 && overwrite != 0); // Try to save the map, if it fails we reset the filename. if (res == 0) { try { write_file(input_name, map_.write()); gui2::show_transient_message(gui_->video(), "", _("Map saved.")); } catch (io_exception& e) { utils::string_map symbols; symbols["msg"] = e.what(); const std::string msg = vgettext("Could not save the map: $msg",symbols); gui2::show_transient_message(gui_->video(), "", msg); } } } void menu_handler::preferences() { preferences::show_preferences_dialog(*gui_, game_config_); gui_->redraw_everything(); } void menu_handler::show_chat_log() { std::string text = recorder.build_chat_log(); gui::show_dialog(*gui_,NULL,_("Chat Log"),"",gui::CLOSE_ONLY,NULL,NULL,"",&text); } void menu_handler::show_help() { help::show_help(*gui_); } void menu_handler::speak() { textbox_info_.show(gui::TEXTBOX_MESSAGE,_("Message:"), has_friends() ? is_observer() ? _("Send to observers only") : _("Send to allies only") : "", preferences::message_private(), *gui_); } void menu_handler::whisper() { preferences::set_message_private(true); speak(); } void menu_handler::shout() { preferences::set_message_private(false); speak(); } bool menu_handler::has_friends() const { if(is_observer()) { return !gui_->observers().empty(); } for(size_t n = 0; n != teams_.size(); ++n) { if(n != gui_->viewing_team() && teams_[gui_->viewing_team()].team_name() == teams_[n].team_name() && teams_[n].is_network()) { return true; } } return false; } bool menu_handler::has_team() const { if(is_observer()) { return false; } for(size_t n = 0; n != teams_.size(); ++n) { if(n != gui_->viewing_team() && teams_[gui_->viewing_team()].team_name() == teams_[n].team_name()) { return true; } } return false; } void menu_handler::recruit(bool browse, int side_num, const map_location &last_hex) { if(browse) return; team ¤t_team = teams_[side_num - 1]; std::vector sample_units; gui_->draw(); //clear the old menu std::vector item_keys; std::vector items; const std::set& recruits = current_team.recruits(); for(std::set::const_iterator it = recruits.begin(); it != recruits.end(); ++it) { const std::map::const_iterator u_type = unit_type_data::types().find_unit_type(*it); if(u_type == unit_type_data::types().end()) { ERR_NG << "could not find unit '" << *it << "'\n"; return; } item_keys.push_back(*it); const unit_type* type = &u_type->second; //display units that we can't afford to recruit in red const char prefix = (type->cost() > current_team.gold() ? font::BAD_TEXT : font::NULL_MARKUP); std::stringstream description; description << font::IMAGE << type->image(); #ifndef LOW_MEM description << "~RC(" << type->flag_rgb() << '>' << team::get_side_colour_index(side_num) << ')'; #endif description << COLUMN_SEPARATOR << font::LARGE_TEXT << prefix << type->type_name() << "\n" << prefix << type->cost() << " " << sngettext("unit^Gold", "Gold", type->cost()); items.push_back(description.str()); sample_units.push_back(type); } if(sample_units.empty()) { gui2::show_transient_message(gui_->video(),"",_("You have no units available to recruit.")); return; } int recruit_res = 0; { dialogs::unit_types_preview_pane unit_preview(*gui_, &map_, sample_units, NULL, side_num); std::vector preview_panes; preview_panes.push_back(&unit_preview); gui::dialog rmenu(*gui_, _("Recruit") + get_title_suffix(side_num), _("Select unit:") + std::string("\n"), gui::OK_CANCEL, gui::dialog::default_style); rmenu.add_button(new help::help_button(*gui_,"recruit_and_recall"), gui::dialog::BUTTON_HELP); rmenu.set_menu(items); rmenu.set_panes(preview_panes); recruit_res = rmenu.show(); } if(recruit_res != -1) { do_recruit(item_keys[recruit_res], side_num, last_hex); } } void menu_handler::repeat_recruit(int side_num, const map_location &last_hex) { if(last_recruit_.empty() == false) do_recruit(last_recruit_, side_num, last_hex); } void menu_handler::do_recruit(const std::string &name, int side_num, const map_location &last_hex) { team ¤t_team = teams_[side_num - 1]; //search for the unit to be recruited in recruits int recruit_num = 0; const std::set& recruits = current_team.recruits(); for(std::set::const_iterator r = recruits.begin(); ; ++r) { if (r == recruits.end()) { return; } if (name == *r) { break; } ++recruit_num; } const std::map::const_iterator u_type = unit_type_data::types().find_unit_type(name); assert(u_type != unit_type_data::types().end()); if(u_type->second.cost() > current_team.gold()) { gui2::show_transient_message(gui_->video(),"", _("You don't have enough gold to recruit that unit")); } else { last_recruit_ = name; //create a unit with traits recorder.add_recruit(recruit_num, last_hex); unit new_unit(&units_, &u_type->second, side_num, true); map_location loc = last_hex; const std::string &msg = recruit_unit(side_num, new_unit, loc, false, gui_ != NULL); if(msg.empty()) { current_team.spend_gold(u_type->second.cost()); statistics::recruit_unit(new_unit); //MP_COUNTDOWN grant time bonus for recruiting current_team.set_action_bonus_count(1 + current_team.action_bonus_count()); redo_stack_.clear(); assert(new_unit.type()); // Dissallow undoing of recruits. Can be enabled again once the unit's // description= key doesn't use random anymore. const bool shroud_cleared = clear_shroud(side_num); if(shroud_cleared || new_unit.type()->genders().size() > 1 || new_unit.type()->has_random_traits()) { clear_undo_stack(side_num); } else { undo_stack_.push_back(undo_action(new_unit,loc,RECRUIT_POS)); } gui_->recalculate_minimap(); gui_->invalidate_game_status(); gui_->invalidate_all(); recorder.add_checksum_check(loc); } else { recorder.undo(); gui2::show_transient_message(gui_->video(),"",msg); } } } void menu_handler::recall(int side_num, const map_location &last_hex) { if(utils::string_bool(level_["disallow_recall"])) { gui2::show_transient_message(gui_->video(),"",_("You are separated from your soldiers and may not recall them")); return; } team ¤t_team = teams_[side_num - 1]; player_info *player = gamestate_.get_player(current_team.save_id()); if(!player) { ERR_NG << "cannot recall a unit for side " << side_num << ", which has no recall list!\n"; return; } std::vector& recall_list_team = current_team.recall_list(); //sort the available units into order by value //so that the most valuable units are shown first sort_units(recall_list_team); gui_->draw(); //clear the old menu if(recall_list_team.empty()) { gui2::show_transient_message(gui_->video(), "" ,_("There are no troops available to recall\n(You must have" " veteran survivors from a previous scenario)")); } else { std::vector options, options_to_filter; std::ostringstream heading; heading << HEADING_PREFIX << COLUMN_SEPARATOR << _("Type") << COLUMN_SEPARATOR << _("Name") << COLUMN_SEPARATOR << _("Level^Lvl.") << COLUMN_SEPARATOR << _("XP"); #ifndef USE_TINY_GUI heading << COLUMN_SEPARATOR << _("Traits"); #endif gui::menu::basic_sorter sorter; sorter.set_alpha_sort(1).set_alpha_sort(2).set_id_sort(3).set_xp_sort(4,3).set_alpha_sort(5); options.push_back(heading.str()); options_to_filter.push_back(options.back()); for(std::vector::const_iterator u = recall_list_team.begin(); u != recall_list_team.end(); ++u) { std::stringstream option, option_to_filter; std::string name = u->name().empty() ? "-" : u->name(); option << IMAGE_PREFIX << u->absolute_image(); #ifndef LOW_MEM option << "~RC(" << u->team_color() << '>' << team::get_side_colour_index(side_num) << ')'; #endif option << COLUMN_SEPARATOR << u->type_name() << COLUMN_SEPARATOR << name << COLUMN_SEPARATOR << u->level() << COLUMN_SEPARATOR << u->experience() << "/"; if (u->can_advance()) option << u->max_experience(); else option << "-"; option_to_filter << u->type_name() << " " << name << " " << u->level(); #ifndef USE_TINY_GUI option << COLUMN_SEPARATOR; const std::vector traits = utils::split(std::string(u->traits_description()), ','); foreach(const std::string& trait, traits) { option << trait << '\n'; option_to_filter << " " << trait; } #endif options.push_back(option.str()); options_to_filter.push_back(option_to_filter.str()); } int res = 0; { gui::dialog rmenu(*gui_, _("Recall") + get_title_suffix(side_num), _("Select unit:") + std::string("\n"), gui::OK_CANCEL, gui::dialog::default_style); rmenu.set_menu(options, &sorter); gui::filter_textbox* filter = new gui::filter_textbox(gui_->video(), _("Filter: "), options, options_to_filter, 1, rmenu, 200); rmenu.set_textbox(filter); delete_recall_unit recall_deleter(*gui_, *filter, recall_list_team, undo_stack_, redo_stack_); gui::dialog_button_info delete_button(&recall_deleter,_("Dismiss Unit")); rmenu.add_button(delete_button); rmenu.add_button(new help::help_button(*gui_,"recruit_and_recall"), gui::dialog::BUTTON_HELP); dialogs::units_list_preview_pane unit_preview(*gui_,&map_,recall_list_team, filter); rmenu.add_pane(&unit_preview); res = rmenu.show(); res = filter->get_index(res); } if(res >= 0) { if(current_team.gold() < game_config::recall_cost) { std::stringstream msg; utils::string_map i18n_symbols; i18n_symbols["cost"] = lexical_cast(game_config::recall_cost); msg << vngettext("You must have at least 1 gold piece to recall a unit", "You must have at least $cost gold pieces to recall a unit", game_config::recall_cost, i18n_symbols); gui::dialog(*gui_,"",msg.str()).show(); } else { LOG_NG << "recall index: " << res << "\n"; unit& un = recall_list_team[res]; map_location loc = last_hex; recorder.add_recall(res,loc); un.set_game_context(&units_); const std::string &err = recruit_unit(side_num, un, loc, true, gui_ != NULL); if(!err.empty()) { recorder.undo(); gui::dialog(*gui_,"",err,gui::OK_ONLY).show(); } else { statistics::recall_unit(un); current_team.spend_gold(game_config::recall_cost); bool shroud_cleared = clear_shroud(side_num); if (shroud_cleared) { clear_undo_stack(side_num); } else { undo_stack_.push_back(undo_action(un,loc,res)); } redo_stack_.clear(); recall_list_team.erase(recall_list_team.begin()+res); gui_->invalidate_game_status(); gui_->invalidate_all(); recorder.add_checksum_check(loc); } } } } } void menu_handler::undo(int side_num) { if(undo_stack_.empty()) return; const events::command_disabler disable_commands; team ¤t_team = teams_[side_num - 1]; undo_action& action = undo_stack_.back(); if (action.is_dismiss) { //undo a dismissal if(!current_team.persistent()) { ERR_NG << "trying to undo a dismissal for side " << side_num << ", which has no recall list!\n"; } else { current_team.recall_list().insert(current_team.recall_list().begin()+action.recall_pos,action.affected_unit); } } else if(action.is_recall()) { if(!current_team.persistent()) { ERR_NG << "trying to undo a recall for side " << side_num << ", which has no recall list!\n"; } else { // Undo a recall action if(units_.count(action.recall_loc) == 0) { return; } const unit& un = units_.find(action.recall_loc)->second; statistics::un_recall_unit(un); current_team.spend_gold(-game_config::recall_cost); current_team.recall_list().insert(current_team.recall_list().begin()+action.recall_pos,un); // invalidate before erasing allow us // to also do the ovelerlapped hexes gui_->invalidate(action.recall_loc); units_.erase(action.recall_loc); gui_->draw(); } } else if(action.is_recruit()) { // Undo a recruit action if(units_.count(action.recall_loc) == 0) { return; } const unit& un = units_.find(action.recall_loc)->second; statistics::un_recruit_unit(un); assert(un.type()); current_team.spend_gold(-un.type()->cost()); //MP_COUNTDOWN take away recruit bonus if(action.countdown_time_bonus) { current_team.set_action_bonus_count(current_team.action_bonus_count() - 1); } // invalidate before erasing allow us // to also do the ovelerlapped hexes gui_->invalidate(action.recall_loc); units_.erase(action.recall_loc); gui_->draw(); } else { // Undo a move action const int starting_moves = action.starting_moves; std::vector route = action.route; std::reverse(route.begin(),route.end()); const unit_map::iterator u = units_.find(route.front()); const unit_map::iterator u_end = units_.find(route.back()); if(u == units_.end() || u_end != units_.end()) { //this can actually happen if the scenario designer has abused the [allow_undo] command ERR_NG << "Illegal 'undo' found. Possible abuse of [allow_undo]?\n"; return; } if(map_.is_village(route.front())) { get_village(route.front(),*gui_,teams_,action.original_village_owner,units_); //MP_COUNTDOWN take away capture bonus if(action.countdown_time_bonus) { current_team.set_action_bonus_count(current_team.action_bonus_count() - 1); } } action.starting_moves = u->second.movement_left(); unit_display::move_unit(route,u->second,teams_); std::pair *up = units_.extract(u->first); up->second.set_goto(map_location()); up->second.set_movement(starting_moves); up->first = route.back(); units_.insert(up); unit::clear_status_caches(); up->second.set_standing(); gui_->invalidate(route.back()); gui_->draw(); } gui_->invalidate_unit(); gui_->invalidate_game_status(); redo_stack_.push_back(action); undo_stack_.pop_back(); recorder.undo(); const bool shroud_cleared = clear_shroud(side_num); if(shroud_cleared) { gui_->recalculate_minimap(); } else { gui_->redraw_minimap(); } } void menu_handler::redo(int side_num) { if(redo_stack_.empty()) return; const events::command_disabler disable_commands; team ¤t_team = teams_[side_num - 1]; undo_action& action = redo_stack_.back(); if (action.is_dismiss) { if(!current_team.persistent()) { ERR_NG << "trying to redo a dismiss for side " << side_num << ", which has no recall list!\n"; } else { //redo a dismissal recorder.add_disband(action.recall_pos); current_team.recall_list().erase(current_team.recall_list().begin()+action.recall_pos); } } else if(action.is_recall()) { if(!current_team.persistent()) { ERR_NG << "trying to redo a recall for side " << side_num << ", which has no recall list!\n"; } else { // Redo recall unit un = current_team.recall_list()[action.recall_pos]; recorder.add_recall(action.recall_pos,action.recall_loc); un.set_game_context(&units_); const std::string &msg = recruit_unit(side_num, un, action.recall_loc, true, gui_ != NULL); if(msg.empty()) { statistics::recall_unit(un); current_team.spend_gold(game_config::recall_cost); current_team.recall_list().erase(current_team.recall_list().begin()+action.recall_pos); gui_->invalidate(action.recall_loc); gui_->draw(); recorder.add_checksum_check(action.recall_loc); } else { recorder.undo(); gui::dialog(*gui_,"",msg,gui::OK_ONLY).show(); } } } else if(action.is_recruit()) { // Redo recruit action map_location loc = action.recall_loc; const std::string name = action.affected_unit.type_id(); //search for the unit to be recruited in recruits int recruit_num = 0; const std::set& recruits = current_team.recruits(); for(std::set::const_iterator r = recruits.begin(); ; ++r) { if (r == recruits.end()) { ERR_NG << "trying to redo a recruit for side " << side_num << ", which does not recruit type \"" << name << "\"\n"; assert(false); return; } if (name == *r) { break; } ++recruit_num; } last_recruit_ = name; recorder.add_recruit(recruit_num,loc); unit new_unit = action.affected_unit; //unit new_unit(action.affected_unit.type(),team_num_,true); const std::string &msg = recruit_unit(side_num, new_unit, loc, false, gui_ != NULL); if(msg.empty()) { current_team.spend_gold(new_unit.type()->cost()); statistics::recruit_unit(new_unit); //MP_COUNTDOWN: restore recruitment bonus current_team.set_action_bonus_count(1 + current_team.action_bonus_count()); gui_->invalidate(action.recall_loc); gui_->draw(); //gui_.invalidate_game_status(); //gui_.invalidate_all(); recorder.add_checksum_check(loc); } else { recorder.undo(); gui::dialog(*gui_,"",msg,gui::OK_ONLY).show(); } } else { // Redo movement action const int starting_moves = action.starting_moves; std::vector route = action.route; const unit_map::iterator u = units_.find(route.front()); if(u == units_.end()) { assert(false); return; } action.starting_moves = u->second.movement_left(); unit_display::move_unit(route,u->second,teams_); std::pair *up = units_.extract(u->first); up->second.set_goto(map_location()); up->second.set_movement(starting_moves); up->first = route.back(); units_.insert(up); unit::clear_status_caches(); up->second.set_standing(); if(map_.is_village(route.back())) { get_village(route.back(),*gui_,teams_,up->second.side()-1,units_); //MP_COUNTDOWN restore capture bonus if(action.countdown_time_bonus) { current_team.set_action_bonus_count(1 + current_team.action_bonus_count()); } } gui_->invalidate(route.back()); gui_->draw(); recorder.add_movement(action.route); } gui_->invalidate_unit(); gui_->invalidate_game_status(); undo_stack_.push_back(action); redo_stack_.pop_back(); } bool menu_handler::clear_shroud(int side_num) { bool cleared = teams_[side_num - 1].auto_shroud_updates() && ::clear_shroud(*gui_, map_, units_, teams_, side_num - 1); return cleared; } void menu_handler::clear_undo_stack(int side_num) { if (!teams_[side_num - 1].auto_shroud_updates()) apply_shroud_changes(undo_stack_, gui_, map_, units_, teams_, side_num - 1); undo_stack_.clear(); } // Highlights squares that an enemy could move to on their turn, showing how many can reach each square. void menu_handler::show_enemy_moves(bool ignore_units, int side_num) { gui_->unhighlight_reach(); // Compute enemy movement positions for(unit_map::iterator u = units_.begin(); u != units_.end(); ++u) { bool invisible = u->second.invisible(u->first, units_, teams_); if (teams_[side_num - 1].is_enemy(u->second.side()) && !gui_->fogged(u->first) && !u->second.incapacitated() && !invisible) { const unit_movement_resetter move_reset(u->second); bool teleports = u->second.get_ability_bool("teleport"); const paths& path = paths(map_,units_, u->first,teams_,false,teleports,teams_[gui_->viewing_team()], 0,false, ignore_units); gui_->highlight_another_reach(path); } } } void menu_handler::toggle_shroud_updates(int side_num) { team ¤t_team = teams_[side_num - 1]; bool auto_shroud = current_team.auto_shroud_updates(); // If we're turning automatic shroud updates on, then commit all moves if (!auto_shroud) update_shroud_now(side_num); current_team.set_auto_shroud_updates(!auto_shroud); } void menu_handler::update_shroud_now(int side_num) { clear_undo_stack(side_num); } bool menu_handler::end_turn(int side_num) { bool unmoved_units = false, partmoved_units = false, some_units_have_moved = false; int units_alive = 0; for(unit_map::const_iterator un = units_.begin(); un != units_.end(); ++un) { if (un->second.side() == side_num) { units_alive++; if(unit_can_move(un->first,un->second,units_,map_,teams_)) { if(!un->second.has_moved()) { unmoved_units = true; } partmoved_units = true; } if(un->second.has_moved()) { some_units_have_moved = true; } } } //Ask for confirmation if the player hasn't made any moves (other than gotos). if (preferences::confirm_no_moves() && units_alive && !some_units_have_moved) { const int res = gui::dialog(*gui_,"",_("You have not started your turn yet. Do you really want to end your turn?"), gui::YES_NO).show(); if(res != 0) { return false; } } // Ask for confirmation if units still have movement left if(preferences::yellow_confirm() && partmoved_units) { const int res = gui::dialog(*gui_,"",_("Some units have movement left. Do you really want to end your turn?"),gui::YES_NO).show(); if (res != 0) { return false; } } else if (preferences::green_confirm() && unmoved_units) { const int res = gui::dialog(*gui_,"",_("Some units have movement left. Do you really want to end your turn?"),gui::YES_NO).show(); if (res != 0) { return false; } } return true; } void menu_handler::goto_leader(int side_num) { unit_map::const_iterator i = units_.find_leader(side_num); if(i != units_.end()) { clear_shroud(side_num); gui_->scroll_to_tile(i->first,game_display::WARP); } } void menu_handler::unit_description(mouse_handler& mousehandler) { const unit_map::const_iterator un = current_unit(mousehandler); if(un != units_.end()) { dialogs::show_unit_description(*gui_, un->second); } } void menu_handler::rename_unit(mouse_handler& mousehandler) { const unit_map::iterator un = current_unit(mousehandler); if (un == units_.end() || gui_->viewing_side() != un->second.side()) return; if(un->second.unrenamable()) return; std::string name = un->second.name(); const int res = gui::show_dialog(*gui_,NULL,_("Rename Unit"),"", gui::OK_CANCEL,NULL,NULL,"",&name); if(res == 0) { recorder.add_rename(name, un->first); un->second.rename(name); gui_->invalidate_unit(); } } unit_map::iterator menu_handler::current_unit(mouse_handler& mousehandler) { unit_map::iterator res = find_visible_unit(units_, mousehandler.get_last_hex(), map_, teams_, teams_[gui_->viewing_team()]); if(res != units_.end()) { return res; } else { return find_visible_unit(units_, mousehandler.get_selected_hex(), map_, teams_, teams_[gui_->viewing_team()]); } } unit_map::const_iterator menu_handler::current_unit(const mouse_handler& mousehandler) const { unit_map::const_iterator res = find_visible_unit(units_, mousehandler.get_last_hex(), map_, teams_, teams_[gui_->viewing_team()]); if(res != units_.end()) { return res; } else { return find_visible_unit(units_, mousehandler.get_selected_hex(), map_, teams_, teams_[gui_->viewing_team()]); } } void menu_handler::create_unit(mouse_handler& mousehandler) { std::vector options; static int last_selection = -1; static bool random_gender = false; std::vector unit_choices; const std::string heading = std::string(1,HEADING_PREFIX) + _("Race") + COLUMN_SEPARATOR + _("Type"); options.push_back(heading); for(unit_type_data::unit_type_map::const_iterator i = unit_type_data::types().begin(); i != unit_type_data::types().end(); ++i) { std::stringstream row; unit_type_data::types().find_unit_type(i->first, unit_type::HELP_INDEX); std::string race; const race_map::const_iterator race_it = unit_type_data::types().races().find(i->second.race()); if (race_it != unit_type_data::types().races().end()) { race = race_it->second.plural_name(); } row << race << COLUMN_SEPARATOR; row << i->second.type_name() << COLUMN_SEPARATOR; options.push_back(row.str()); unit_choices.push_back(&(i->second)); } int choice = 0; bool random_gender_choice = random_gender; { gui::dialog umenu(*gui_, _("Create Unit (Debug!)"), "", gui::OK_CANCEL); umenu.add_option( (formatter()<<_("Gender: ")<<_("gender^Random")).str(), random_gender_choice, gui::dialog::BUTTON_EXTRA ); gui::menu::basic_sorter sorter; sorter.set_alpha_sort(0).set_alpha_sort(1); umenu.set_menu(options, &sorter); gui::filter_textbox* filter = new gui::filter_textbox(gui_->video(), _("Filter: "), options, options, 1, umenu, 200); umenu.set_textbox(filter); //sort by race then by type name umenu.get_menu().sort_by(1); umenu.get_menu().sort_by(0); if (last_selection >= 0) umenu.get_menu().move_selection(last_selection); else umenu.get_menu().reset_selection(); dialogs::unit_types_preview_pane unit_preview(*gui_, &map_, unit_choices, filter, 1, dialogs::unit_types_preview_pane::SHOW_ALL); umenu.add_pane(&unit_preview); unit_preview.set_selection(umenu.get_menu().selection()); choice = umenu.show(); choice = filter->get_index(choice); random_gender_choice = umenu.option_checked(0); } if (size_t(choice) < unit_choices.size()) { last_selection = choice; random_gender = random_gender_choice; const std::vector& genders = (*unit_choices[choice]).genders(); const unit_race::GENDER gender = (!genders.empty() ? genders[gamestate_.rng().get_random() % genders.size()] : unit_race::MALE); unit chosen(&units_, unit_choices[choice], 1, false, false, gender, "", random_gender); chosen.new_turn(); const map_location& loc = mousehandler.get_last_hex(); units_.replace(loc, chosen); if(map_.is_village(loc)) { int team = chosen.side() - 1; // translate to 0-based team number get_village(loc, *gui_, teams_, team, units_); } gui_->invalidate(loc); gui_->invalidate_unit(); } } void menu_handler::change_side(mouse_handler& mousehandler) { const map_location& loc = mousehandler.get_last_hex(); const unit_map::iterator i = units_.find(loc); if(i == units_.end()) { if(!map_.is_village(loc)) return; // village_owner returns -1 for free village, so team 0 will get it int team = village_owner(loc, teams_) + 1; // team is 0-based so team=team::nteams() is not a team // but this will make get_village free it if(team > team::nteams()) { team = 0; } get_village(loc, *gui_, teams_, team, units_); } else { int side = i->second.side(); ++side; if(side > team::nteams()) { side = 1; } i->second.set_side(side); if(map_.is_village(loc)) { int team = side - 1; // translate to 0-based team number get_village(loc, *gui_, teams_, team, units_); } } } void menu_handler::label_terrain(mouse_handler& mousehandler, bool team_only) { const map_location& loc = mousehandler.get_last_hex(); if (map_.on_board(loc) == false) { return; } gui::dialog d(*gui_, _("Place Label"), "", gui::OK_CANCEL); const terrain_label* old_label = gui_->labels().get_label(loc); d.set_textbox(_("Label: "), (old_label ? old_label->text() : ""), map_labels::get_max_chars()); d.add_option(_("Team only"), team_only, gui::dialog::BUTTON_CHECKBOX_LEFT); if(!d.show()) { std::string team_name; SDL_Color colour = font::LABEL_COLOUR; if (d.option_checked()) { team_name = gui_->labels().team_name(); } else { colour = int_to_color(team::get_side_rgb(gui_->viewing_side())); } const std::string& old_team_name = old_label ? old_label->team_name() : ""; // remove the old label if we changed the team_name if (d.option_checked() == (old_team_name == "")) { const terrain_label* old = gui_->labels().set_label(loc, "", old_team_name, colour); if (old) recorder.add_label(old); } const terrain_label* res = gui_->labels().set_label(loc, d.textbox_text(), team_name, colour); if (res) recorder.add_label(res); } } void menu_handler::clear_labels() { if (gui_->team_valid() && !is_observer()) { gui_->labels().clear(gui_->current_team_name()); recorder.clear_labels(gui_->current_team_name()); } } void menu_handler::continue_move(mouse_handler &mousehandler, int side_num) { unit_map::iterator i = current_unit(mousehandler); if(i == units_.end() || i->second.move_interrupted() == false) { i = units_.find(mousehandler.get_selected_hex()); if (i == units_.end() || i->second.move_interrupted() == false) return; } move_unit_to_loc(i, i->second.get_interrupted_move(), true, side_num, mousehandler); } void menu_handler::move_unit_to_loc(const unit_map::const_iterator &ui, const map_location& target, bool continue_move, int side_num, mouse_handler &mousehandler) { assert(ui != units_.end()); marked_route route = mousehandler.get_route(ui, target, teams_[side_num - 1]); if(route.steps.empty()) return; assert(route.steps.front() == ui->first); gui_->set_route(&route); move_unit(gui_,NULL,map_,units_,teams_,route.steps,&recorder,&undo_stack_,NULL,continue_move); gui_->invalidate_game_status(); } void menu_handler::toggle_ellipses() { preferences::set_ellipses(!preferences::ellipses()); gui_->invalidate_all(); } void menu_handler::toggle_grid() { preferences::set_grid(!preferences::grid()); gui_->invalidate_all(); } void menu_handler::unit_hold_position(mouse_handler &mousehandler, int side_num) { const unit_map::iterator un = units_.find(mousehandler.get_selected_hex()); if (un != units_.end() && un->second.side() == side_num && un->second.movement_left() >= 0) { un->second.set_hold_position(!un->second.hold_position()); gui_->invalidate(mousehandler.get_selected_hex()); mousehandler.set_current_paths(paths()); gui_->draw(); if(un->second.hold_position()) { un->second.set_user_end_turn(true); mousehandler.cycle_units(false); } } } void menu_handler::end_unit_turn(mouse_handler &mousehandler, int side_num) { const unit_map::iterator un = units_.find(mousehandler.get_selected_hex()); if (un != units_.end() && un->second.side() == side_num && un->second.movement_left() >= 0) { un->second.set_user_end_turn(!un->second.user_end_turn()); if(un->second.hold_position() && !un->second.user_end_turn()){ un->second.set_hold_position(false); } gui_->invalidate(mousehandler.get_selected_hex()); mousehandler.set_current_paths(paths()); gui_->draw(); if(un->second.user_end_turn()) { mousehandler.cycle_units(false); } } } void menu_handler::search() { std::ostringstream msg; msg << _("Search"); if(last_search_hit_.valid()) { msg << " [" << last_search_ << "]"; } msg << ':'; textbox_info_.show(gui::TEXTBOX_SEARCH,msg.str(), "", false, *gui_); } void menu_handler::do_speak(){ //None of the two parameters really needs to be passed since the informations belong to members of the class. //But since it makes the called method more generic, it is done anyway. chat_handler::do_speak(textbox_info_.box()->text(),textbox_info_.check() != NULL ? textbox_info_.check()->checked() : false); } void menu_handler::add_chat_message(const time_t& time, const std::string& speaker, int side, const std::string& message, events::chat_handler::MESSAGE_TYPE type) { gui_->add_chat_message(time, speaker, side, message, type, false); } //simple command args parser, separated from command_handler for clarity. //a word begins with a nonspace //n-th arg is n-th word up to the next space //n-th data is n-th word up to the end //cmd is 0-th arg, begins at 0 always. class cmd_arg_parser { public: cmd_arg_parser() : str_(""), args(1, 0), args_end(false) { } explicit cmd_arg_parser(const std::string& str) : str_(str), args(1, 0), args_end(false) { } void parse(const std::string& str) { str_ = str; args.clear(); args.push_back(0); args_end = false; } const std::string& get_str() const { return str_; } std::string get_arg(unsigned n) const { advance_to_arg(n); if (n < args.size()) { return std::string(str_, args[n], str_.find(' ', args[n]) - args[n]); } else { return ""; } } std::string get_data(unsigned n) const { advance_to_arg(n); if (n < args.size()) { return std::string(str_, args[n]); } else { return ""; } } std::string get_cmd() const { return get_arg(0); } private: cmd_arg_parser& operator=(const cmd_arg_parser&); cmd_arg_parser(const cmd_arg_parser&); void advance_to_arg(unsigned n) const { while (n < args.size() && !args_end) { size_t first_space = str_.find_first_of(' ', args.back()); size_t next_arg_begin = str_.find_first_not_of(' ', first_space); if (next_arg_begin != std::string::npos) { args.push_back(next_arg_begin); } else { args_end = true; } } } std::string str_; mutable std::vector args; mutable bool args_end; }; //A helper class template with a slim public interface //This represents a map of strings to void()-member-function-of-Worker-pointers //with all the common functionality like general help, command help and aliases //Usage (of a derived class): Derived(specific-arguments) d; d.dispatch(command); //Derived classes should override virtual functions where noted. //The template parameter currently must be the dervived class itself, //i.e. class X : public map_command_handler //To add a new command in a derived class: // * add a new private void function() to the derived class // * add it to the function map in init_map there, setting flags like // "D" for debug only (checking the flag is also done in the derived class) // * remember to add some help and/or usage information in init_map() template class map_command_handler { public: typedef void (Worker::*command_handler)(); struct command { command_handler handler; std::string help; //long help text std::string usage; //only args info std::string flags; explicit command(command_handler h, const std::string help="", const std::string& usage="", const std::string flags="") : handler(h), help(help), usage(usage), flags(flags) { } bool has_flag(const char f) const { return flags.find(f) != flags.npos; } command& add_flag(const char f) { flags += f; return *this; } }; typedef std::map command_map; typedef std::map command_alias_map; map_command_handler() : cap_("") { } virtual ~map_command_handler() {} bool empty() const { return command_map_.empty(); } bool has_command(const std::string& cmd) const { return get_command(cmd) != 0; } //actual work function void dispatch(std::string cmd) { if (empty()) { init_map_default(); init_map(); } // We recursively resolve alias (100 max to avoid infinite recursion) for (int i=0; i < 100; ++i) { parse_cmd(cmd); std::string actual_cmd = get_actual_cmd(get_cmd()); if (actual_cmd == get_cmd()) break; std::string data = get_data(1); // translate the command and add space + data if any cmd = actual_cmd + (data.empty() ? "" : " ") + data; } if (get_cmd().empty()) { return; } if (const command* c = get_command(get_cmd())) { if (is_enabled(*c)) { (static_cast(this)->*(c->handler))(); } else { print(get_cmd(), _("This command is currently unavailable.")); } } else if (help_on_unknown_) { print("help", "Unknown command (" + get_cmd() + "), try " + cmd_prefix_ + "help " "for a list of available commands."); } } protected: void init_map_default() { register_command("help", &map_command_handler::help, _("Available commands list and command-specific help. " "Use \"help all\" to include currently unavailable commands."), "[all|]"); } //derived classes initialize the map overriding this function virtual void init_map() = 0; //overriden in derived classes to actually print the messages somwehere virtual void print(const std::string& title, const std::string& message) = 0; //should be overriden in derived classes if the commands have flags //this should return a string describing what all the flags mean virtual std::string get_flags_description() const { return ""; } //this should return a string describing the flags of the given command virtual std::string get_command_flags_description(const command& /*c*/) const { return ""; } //this should be overriden if e.g. flags are used to control command //availability. Return false if the command should not be executed by dispatch() virtual bool is_enabled(const command& /*c*/) const { return true; } virtual void parse_cmd(const std::string& cmd_string) { cap_.parse(cmd_string); } //safe n-th argunment getter virtual std::string get_arg(unsigned argn) const { return cap_.get_arg(argn); } //"data" is n-th arg and everything after it virtual std::string get_data(unsigned argn = 1) const { return cap_.get_data(argn); } virtual std::string get_cmd() const { return cap_.get_cmd(); } //command error reporting shorthands void command_failed(const std::string& message) { print(get_cmd(), "Error: " + message); } void command_failed_need_arg(int argn) { command_failed("Missing argument " + lexical_cast(argn)); } void print_usage() { help_command(get_cmd()); } //take aliases into account std::string get_actual_cmd(const std::string& cmd) const { command_alias_map::const_iterator i = command_alias_map_.find(cmd); return i != command_alias_map_.end() ? i->second : cmd; } const command* get_command(const std::string& cmd) const { typename command_map::const_iterator i = command_map_.find(cmd); return i != command_map_.end() ? &i->second : 0; } command* get_command(const std::string& cmd) { typename command_map::iterator i = command_map_.find(cmd); return i != command_map_.end() ? &i->second : 0; } void help() { //print command-specific help if available, otherwise list commands if (help_command(get_arg(1))) { return; } std::stringstream ss; bool show_unavail = show_unavailable_ || get_arg(1) == "all"; BOOST_FOREACH(typename command_map::value_type i, command_map_) { if (show_unavail || is_enabled(i.second)) { ss << i.first; //if (!i.second.usage.empty()) { // ss << " " << i.second.usage; //} //uncomment the above to display usage information in command list //which might clutter it somewhat if (!i.second.flags.empty()) { ss << " (" << i.second.flags << ") "; } ss << "; "; } } print("help", "Available commands " + get_flags_description() + ":\n" + ss.str()); print("help", "Type " + cmd_prefix_ + "help for more info."); } //returns true if the command exists. bool help_command(const std::string& acmd) { std::string cmd = get_actual_cmd(acmd); const command* c = get_command(cmd); if (c) { std::stringstream ss; ss << cmd_prefix_ << cmd; if (c->help.empty() && c->usage.empty()) { ss << _(" No help available."); } else { ss << " - " << c->help; } if (!c->usage.empty()) { ss << " Usage: " << cmd_prefix_ << cmd << " " << c->usage; } ss << get_command_flags_description(*c); const std::vector l = get_aliases(cmd); if (!l.empty()) { ss << " (aliases: " << utils::join(l,' ') << ")"; } print("help", ss.str()); } return c != 0; } cmd_arg_parser cap_; protected: //show a "try help" message on unknown command? static void set_help_on_unknown(bool value) { help_on_unknown_ = value; } //show all commands in help regardless whether they are active static void set_show_unavailable(bool value) { show_unavailable_ = value; } //this is display-only static void set_cmd_prefix(std::string value) { cmd_prefix_ = value; } virtual void register_command(const std::string& cmd, command_handler h, const std::string& help="", const std::string& usage="", const std::string& flags="") { command c = command(h, help, usage, flags); std::pair r; r = command_map_.insert(typename command_map::value_type(cmd, c)); if (!r.second) { //overwrite if exists r.first->second = c; } } virtual void assert_existence(const std::string& cmd) { assert(command_map_.count(cmd)); } virtual void register_alias(const std::string& to_cmd, const std::string& cmd) { // disable the assert to allow alias to "command + args" // the fonction assert_existence seems unused now //assert_existence(to_cmd); command_alias_map_[cmd] = to_cmd; } //get all aliases of a command. static const std::vector get_aliases(const std::string& cmd) { std::vector aliases; typedef command_alias_map::value_type p; BOOST_FOREACH(p i, command_alias_map_) { if (i.second == cmd) { aliases.push_back(i.first); } } return aliases; } private: static command_map command_map_; static command_alias_map command_alias_map_; static bool help_on_unknown_; static bool show_unavailable_; static std::string cmd_prefix_; }; //static member definitions template typename map_command_handler::command_map map_command_handler::command_map_; template typename map_command_handler::command_alias_map map_command_handler::command_alias_map_; template bool map_command_handler::help_on_unknown_ = true; template bool map_command_handler::show_unavailable_ = false; template std::string map_command_handler::cmd_prefix_; //command handler for chat /commands class chat_command_handler : public map_command_handler { public: typedef map_command_handler map; chat_command_handler(chat_handler& chathandler, bool allies_only) : map(), chat_handler_(chathandler), allies_only_(allies_only) { } protected: void do_emote(); void do_network_send(); void do_network_send_req_arg(); void do_room_query(); void do_room_query_noarg(); void do_whisper(); void do_chanmsg(); void do_log(); void do_ignore(); void do_friend(); void do_remove(); void do_display(); void do_version(); /** Ask the server to register the currently used nick. */ void do_register(); /** Ask the server do drop the currently used (and registered) nick. */ void do_drop(); /** Update details for the currently used username. */ void do_set(); /** Request information about a user from the server. */ void do_info(); /** * Request a list of details that can be set for a username * as these might vary depending on the configuration of the server. */ void do_details(); std::string get_flags_description() const { return "(A) - auth command"; } std::string get_command_flags_description( const map_command_handler::command& c) const { return std::string(c.has_flag('A') ? " (auth only)" : ""); } bool is_enabled( const map_command_handler::command& c) const { return !(c.has_flag('A') && !preferences::is_authenticated()); } void print(const std::string& title, const std::string& message) { chat_handler_.add_chat_message(time(NULL), title, 0, message); } void init_map() { set_cmd_prefix("/"); register_command("query", &chat_command_handler::do_network_send, _("Send a query to the server. Without arguments the server" " should tell you the available commands.")); register_command("ban", &chat_command_handler::do_network_send_req_arg, _("Ban and kick a player or observer. If he is not in the" " game but on the server he will only be banned."), ""); register_command("unban", &chat_command_handler::do_network_send_req_arg, _("Unban a user. He does not have to be in the game but on" " the server."), ""); register_command("kick", &chat_command_handler::do_network_send_req_arg, _("Kick a player or observer."), ""); register_command("mute", &chat_command_handler::do_network_send, _("Mute an observer. Without an argument displays the mute status."), ""); register_command("unmute", &chat_command_handler::do_network_send, _("Unmute an observer. Without an argument unmutes everyone."), ""); register_command("muteall", &chat_command_handler::do_network_send, _("Mute/Unmute all observers. (toggles)"), ""); register_command("ping", &chat_command_handler::do_network_send, ""); register_command("green", &chat_command_handler::do_network_send_req_arg, "", "", "A"); register_command("red", &chat_command_handler::do_network_send_req_arg, "", "", "A"); register_command("yellow", &chat_command_handler::do_network_send_req_arg, "", "", "A"); register_command("adminmsg", &chat_command_handler::do_network_send_req_arg, _("Send a message to the server admins currently online"), ""); register_command("emote", &chat_command_handler::do_emote, _("Send an emotion or personal action in chat."), ""); register_alias("emote", "me"); register_command("whisper", &chat_command_handler::do_whisper, _("Sends a private message. " "You can't send messages to players that don't control " "a side in a running game you are in."), " "); register_alias("whisper", "msg"); register_alias("whisper", "m"); register_command("log", &chat_command_handler::do_log, _("Change the log level of a log domain."), " "); register_command("ignore", &chat_command_handler::do_ignore, _("Add a nick to your ignores list."), ""); register_command("friend", &chat_command_handler::do_friend, _("Add a nick to your friends list."), ""); register_command("remove", &chat_command_handler::do_remove, _("Remove a nick from your ignores or friends list."), ""); register_command("list", &chat_command_handler::do_display, _("Show your ignores and friends list.")); register_alias("list", "display"); register_command("version", &chat_command_handler::do_version, _("Display version information.")); register_command("register", &chat_command_handler::do_register, _("Register your nick"), " "); register_command("drop", &chat_command_handler::do_drop, _("Drop your nick.")); register_command("set", &chat_command_handler::do_set, _("Update details for your nick. For possible details see '/details'."), " "); register_command("info", &chat_command_handler::do_info, _("Request information about a nick."), ""); register_command("details", &chat_command_handler::do_details, _("Request a list of details you can set for your registered nick.")); register_command("join", &chat_command_handler::do_network_send_req_arg, _("Join a room."), ""); register_command("part", &chat_command_handler::do_network_send_req_arg, _("Part a room."), ""); register_command("names", &chat_command_handler::do_room_query, _("List room members."), ""); register_command("rooms", &chat_command_handler::do_room_query_noarg, _("List available rooms.")); register_command("room", &chat_command_handler::do_chanmsg, _("Room msg."), " "); } private: chat_handler& chat_handler_; bool allies_only_; }; //command handler for user :commands. Also understands all chat commands //via inheritance. This complicates some things a bit. class console_handler : public map_command_handler, private chat_command_handler { public: //convenience typedef typedef map_command_handler chmap; console_handler(menu_handler& menu_handler, mouse_handler& mouse_handler, const unsigned int team_num) : chmap(), chat_command_handler(menu_handler, true), menu_handler_(menu_handler), mouse_handler_(mouse_handler) , team_num_(team_num) { } using chmap::dispatch; //disambiguate protected: //chat_command_handler's init_map() and hanlers will end up calling these. //this makes sure the commands end up in our map virtual void register_command(const std::string& cmd, chat_command_handler::command_handler h, const std::string& help="", const std::string& usage="", const std::string& flags="") { chmap::register_command(cmd, h, help, usage, flags + "N"); //add chat commands as network_only } virtual void assert_existence(const std::string& cmd) { chmap::assert_existence(cmd); } virtual void register_alias(const std::string& to_cmd, const std::string& cmd) { chmap::register_alias(to_cmd, cmd); } virtual std::string get_arg(unsigned i) const { return chmap::get_arg(i); } virtual std::string get_cmd() const { return chmap::get_cmd(); } virtual std::string get_data(unsigned n = 1) const { return chmap::get_data(n); } //these are needed to avoid ambiguities introduced by inheriting from console_command_handler using chmap::register_command; using chmap::register_alias; using chmap::help; using chmap::is_enabled; using chmap::command_failed; using chmap::command_failed_need_arg; void do_refresh(); void do_droid(); void do_theme(); void do_control(); void do_clear(); void do_sunset(); void do_fps(); void do_benchmark(); void do_save(); void do_save_quit(); void do_quit(); void do_ignore_replay_errors(); void do_nosaves(); void do_next_level(); void do_choose_level(); void do_debug(); void do_nodebug(); void do_lua(); void do_custom(); void do_set_alias(); void do_set_var(); void do_show_var(); void do_unit(); // void do_buff(); // void do_unbuff(); void do_discover(); void do_undiscover(); void do_create(); void do_fog(); void do_shroud(); void do_gold(); void do_event(); void do_toggle_draw_coordinates(); void do_toggle_draw_terrain_codes(); std::string get_flags_description() const { return "(D) - debug only, (N) - network only, (A) - auth only"; } using chat_command_handler::get_command_flags_description; //silence a warning std::string get_command_flags_description(const chmap::command& c) const { return std::string(c.has_flag('D') ? " (debug command)" : "") + std::string(c.has_flag('N') ? " (network only)" : "") + std::string(c.has_flag('A') ? " (auth only)" : ""); } using map::is_enabled; bool is_enabled(const chmap::command& c) const { return !((c.has_flag('D') && !game_config::debug) || (c.has_flag('N') && network::nconnections() == 0) || (c.has_flag('A') && !preferences::is_authenticated())); } void print(const std::string& title, const std::string& message) { menu_handler_.add_chat_message(time(NULL), title, 0, message); } void init_map() { chat_command_handler::init_map();//grab chat_ /command handlers chmap::get_command("log")->flags = ""; //clear network-only flag from log chmap::get_command("version")->flags = ""; //clear network-only flag chmap::get_command("ignore")->flags = ""; //clear network-only flag chmap::get_command("friend")->flags = ""; //clear network-only flag chmap::get_command("list")->flags = ""; //clear network-only flag chmap::get_command("remove")->flags = ""; //clear network-only flag chmap::set_cmd_prefix(":"); register_command("refresh", &console_handler::do_refresh, _("Refresh gui.")); register_command("droid", &console_handler::do_droid, _("Switch a side to/from AI control."), "[ [on/off]]"); register_command("theme", &console_handler::do_theme); register_command("control", &console_handler::do_control, _("Assign control of a side to a different player or observer."), " ", "N"); register_command("clear", &console_handler::do_clear, _("Clear chat history.")); register_command("sunset", &console_handler::do_sunset, _("Visualize the screen refresh procedure."), "", "D"); register_command("fps", &console_handler::do_fps, "Show fps."); register_command("benchmark", &console_handler::do_benchmark); register_command("save", &console_handler::do_save, _("Save game.")); register_alias("save", "w"); register_command("quit", &console_handler::do_quit, _("Quit game.")); register_alias("quit", "q"); register_alias("quit", "q!"); register_command("save_quit", &console_handler::do_save_quit, _("Save and quit.")); register_alias("save_quit", "wq"); register_command("ignore_replay_errors", &console_handler::do_ignore_replay_errors, _("Ignore replay errors.")); register_command("nosaves", &console_handler::do_nosaves, _("Disable autosaves.")); register_command("next_level", &console_handler::do_next_level, _("Advance to the next scenario, or scenario identified by 'id'"), "", "D"); register_alias("next_level", "n"); register_command("choose_level", &console_handler::do_choose_level, _("Choose next scenario"), "", "D"); register_alias("choose_level", "cl"); register_command("debug", &console_handler::do_debug, _("Turn debug mode on.")); register_command("nodebug", &console_handler::do_nodebug, _("Turn debug mode off."), "", "D"); register_command("lua", &console_handler::do_lua, _("Execute a Lua statement.")); register_command("custom", &console_handler::do_custom, _("Set the command used by the custom command hotkey"), "[;...]"); register_command("alias", &console_handler::do_set_alias, _("Set or show alias to a command"), "[=]"); register_command("set_var", &console_handler::do_set_var, _("Set a scenario variable."), "=", "D"); register_command("show_var", &console_handler::do_show_var, _("Show a scenario variable."), "", "D"); register_command("unit", &console_handler::do_unit, _("Modify a unit variable. (Only top level keys are supported.)"), "", "D"); /* register_command("buff", &console_handler::do_buff, _("Add a trait to a unit."), "", "D"); register_command("unbuff", &console_handler::do_unbuff, _("Remove a trait from a unit. (Does not work yet.)"), "", "D");*/ register_command("discover", &console_handler::do_discover, _("Discover all units in help."), ""); // Hide to prevent unwanted clearing of user's progress /* register_command("undiscover", &console_handler::do_undiscover, _("'Undiscover' all units in help."), "");*/ register_command("create", &console_handler::do_create, _("Create a unit."), "", "D"); register_command("fog", &console_handler::do_fog, _("Toggle fog for the current player."), "", "D"); register_command("shroud", &console_handler::do_shroud, _("Toggle shroud for the current player."), "", "D"); register_command("gold", &console_handler::do_gold, _("Give gold to the current player."), "", "D"); register_command("throw", &console_handler::do_event, _("Fire a game event."), "", "D"); register_alias("throw", "fire"); register_command("show_coordinates", &console_handler::do_toggle_draw_coordinates, _("Toggle overlaying of x,y coordinates on hexes.")); register_alias("show_coordinates", "sc"); register_command("show_terrain_codes", &console_handler::do_toggle_draw_terrain_codes, _("Toggle overlaying of terrain codes on hexes.")); register_alias("show_terrain_codes", "tc"); if (const config &alias_list = preferences::get_alias()) { foreach (const config::attribute &a, alias_list.attribute_range()) { register_alias(a.second, a.first); } } } private: menu_handler& menu_handler_; mouse_handler& mouse_handler_; const unsigned int team_num_; }; chat_handler::chat_handler() { } chat_handler::~chat_handler() { } /** * Change the log level of a log domain. * * @param data string of the form: "@ @" */ void chat_handler::change_logging(const std::string& data) { const std::string::const_iterator j = std::find(data.begin(), data.end(), ' '); if (j == data.end()) return; const std::string level(data.begin(),j); const std::string domain(j+1,data.end()); int severity; if (level == "error") severity = 0; else if (level == "warning") severity = 1; else if (level == "info") severity = 2; else if (level == "debug") severity = 3; else { utils::string_map symbols; symbols["level"] = level; const std::string& msg = vgettext("Unknown debug level: '$level'.", symbols); ERR_NG << msg << "\n"; add_chat_message(time(NULL), _("error"), 0, msg); return; } if (!lg::set_log_domain_severity(domain, severity)) { utils::string_map symbols; symbols["domain"] = domain; const std::string& msg = vgettext("Unknown debug domain: '$domain'.", symbols); ERR_NG << msg << "\n"; add_chat_message(time(NULL), _("error"), 0, msg); return; } else { utils::string_map symbols; symbols["level"] = level; symbols["domain"] = domain; const std::string& msg = vgettext("Switched domain: '$domain' to level: '$level'.", symbols); LOG_NG << msg << "\n"; add_chat_message(time(NULL), "log", 0, msg); } } void chat_handler::send_command(const std::string& cmd, const std::string& args /* = "" */) { config data; if (cmd == "muteall") { data.add_child(cmd); } else if (cmd == "query") { data.add_child(cmd)["type"] = args; } else if (cmd == "ban" || cmd == "unban" || cmd == "kick" || cmd == "mute" || cmd == "unmute") { data.add_child(cmd)["username"] = args; } else if (cmd == "ping") { data[cmd] = lexical_cast(time(NULL)); } else if (cmd == "green") { data.add_child("query")["type"] = "lobbymsg @" + args; } else if (cmd == "red") { data.add_child("query")["type"] = "lobbymsg #" + args; } else if (cmd == "yellow") { data.add_child("query")["type"] = "lobbymsg <255,255,0>" + args; } else if (cmd == "adminmsg") { data.add_child("query")["type"] = "adminmsg " + args; } else if (cmd == "join") { data.add_child("room_join")["room"] = args; } else if (cmd == "part") { data.add_child("room_part")["room"] = args; } network::send_data(data, 0, true); } void chat_handler::do_speak(const std::string& message, bool allies_only) { if(message == "" || message == "/") { return; } bool is_command = (message[0] == '/'); bool quoted_command = (is_command && message[1] == ' '); if(!is_command) { send_chat_message(message, allies_only); return; } else if (quoted_command) { send_chat_message(std::string(message.begin() + 2, message.end()), allies_only); return; } std::string cmd(message.begin() + 1, message.end()); chat_command_handler cch(*this, allies_only); cch.dispatch(cmd); } void chat_handler::send_whisper(const std::string& receiver, const std::string& message) { config cwhisper, data; cwhisper["receiver"] = receiver; cwhisper["message"] = message; cwhisper["sender"] = preferences::login(); data.add_child("whisper", cwhisper); network::send_data(data, 0, true); } void chat_handler::add_whisper_sent(const std::string& receiver, const std::string& message) { add_chat_message(time(NULL), "whisper to " + receiver, 0, message); } void chat_handler::add_whisper_received(const std::string& sender, const std::string& message) { add_chat_message(time(NULL), "whisper: " + sender, 0, message); } void chat_handler::send_chat_room_message(const std::string& room, const std::string& message) { config cmsg, data; cmsg["room"] = room; cmsg["message"] = message; cmsg["sender"] = preferences::login(); data.add_child("message", cmsg); network::send_data(data, 0, true); } void chat_handler::add_chat_room_message_sent(const std::string &room, const std::string &message) { add_chat_room_message_received(room, preferences::login(), message); } void chat_handler::add_chat_room_message_received(const std::string &room, const std::string &speaker, const std::string &message) { add_chat_message(time(NULL), room + ": " + speaker, 0, message, events::chat_handler::MESSAGE_PRIVATE); } void chat_command_handler::do_emote() { chat_handler_.send_chat_message("/me " + get_data(), allies_only_); } void chat_command_handler::do_network_send() { chat_handler_.send_command(get_cmd(), get_data()); } void chat_command_handler::do_network_send_req_arg() { if (get_data(1).empty()) return command_failed_need_arg(1); do_network_send(); } void chat_command_handler::do_room_query_noarg() { config data; config& q = data.add_child("room_query"); q.add_child(get_cmd()); network::send_data(data, 0, true); } void chat_command_handler::do_room_query() { if (get_data(1).empty()) return command_failed_need_arg(1); config data; config& q = data.add_child("room_query"); q["room"] = get_data(); q.add_child(get_cmd()); network::send_data(data, 0, true); } void chat_command_handler::do_whisper() { if (get_data(1).empty()) return command_failed_need_arg(1); if (get_data(2).empty()) return command_failed_need_arg(2); chat_handler_.send_whisper(get_arg(1), get_data(2)); chat_handler_.add_whisper_sent(get_arg(1), get_data(2)); } void chat_command_handler::do_chanmsg() { if (get_data(1).empty()) return command_failed_need_arg(1); if (get_data(2).empty()) return command_failed_need_arg(2); chat_handler_.send_chat_room_message(get_arg(1), get_data(2)); chat_handler_.add_chat_room_message_sent(get_arg(1), get_data(2)); } void chat_command_handler::do_log() { chat_handler_.change_logging(get_data()); } void chat_command_handler::do_ignore() { if (get_arg(1).empty()) { const std::set& tmp = preferences::get_ignores(); print("ignores list", tmp.empty() ? "(empty)" : utils::join(tmp)); } else { for(int i = 1; !get_arg(i).empty(); i++){ if (preferences::add_ignore(get_arg(i))) { print("ignore", _("Added to ignore list: ") + get_arg(i)); } else { command_failed(_("Invalid username: ") + get_arg(i)); } } } } void chat_command_handler::do_friend() { if (get_arg(1).empty()) { const std::set& tmp = preferences::get_friends(); print("friends list", tmp.empty() ? "(empty)" : utils::join(tmp)); } else { for(int i = 1;!get_arg(i).empty();i++){ if (preferences::add_friend(get_arg(i))) { print("friend", _("Added to friends list: ") + get_arg(i)); } else { command_failed(_("Invalid username: ") + get_arg(i)); } } } } void chat_command_handler::do_remove() { for(int i = 1;!get_arg(i).empty();i++){ preferences::remove_friend(get_arg(i)); preferences::remove_ignore(get_arg(i)); print("list", _("Removed from list: ") + get_arg(i)); } } void chat_command_handler::do_display() { const std::set & friends = preferences::get_friends(); const std::set & ignores = preferences::get_ignores(); if (!friends.empty()) { print("friends list", utils::join(friends)); } if (!ignores.empty()) { print("ignores list", utils::join(ignores)); } if (friends.empty() && ignores.empty()) { print("list", _("There are no players on your friends or ignore list.")); } } void chat_command_handler::do_version() { print("version", game_config::revision); } void chat_command_handler::do_register() { config data; config& nickserv = data.add_child("nickserv"); if (get_data(1).empty()) return command_failed_need_arg(1); config ® = nickserv.add_child("register"); reg["password"] = get_arg(1); if(!get_data(2).empty()) { reg["mail"] = get_arg(2); } print("nick registration", "registering with password *** and " + (get_data(2).empty() ? "no email address" : "email address " + get_data(2))); network::send_data(data, 0, true); } void chat_command_handler::do_drop() { config data; config& nickserv = data.add_child("nickserv"); nickserv.add_child("drop"); print("nick registration", "dropping your username"); network::send_data(data, 0, true); } void chat_command_handler::do_set() { config data; config& nickserv = data.add_child("nickserv"); if (get_data(1).empty()) return command_failed_need_arg(1); if (get_data(2).empty()) return command_failed_need_arg(2); config &set = nickserv.add_child("set"); set["detail"] = get_arg(1); set["value"] = get_data(2); print("nick registration", "setting " + get_arg(1) + " to " + get_data(2)); network::send_data(data, 0, true); } void chat_command_handler::do_info() { if (get_data(1).empty()) return command_failed_need_arg(1); config data; config& nickserv = data.add_child("nickserv"); nickserv.add_child("info")["name"] = get_data(1); print("nick registration", "requesting information for user " + get_data(1)); network::send_data(data, 0, true); } void chat_command_handler::do_details() { config data; config& nickserv = data.add_child("nickserv"); nickserv.add_child("details"); network::send_data(data, 0, true); } void menu_handler::send_chat_message(const std::string& message, bool allies_only) { config cfg; cfg["id"] = preferences::login(); cfg["message"] = message; const int side = is_observer() ? 0 : gui_->viewing_side(); if(!is_observer()) { cfg["side"] = lexical_cast(side); } bool private_message = has_friends() && allies_only; if(private_message) { if (is_observer()) { cfg["team_name"] = game_config::observer_team_name; } else { cfg["team_name"] = teams_[gui_->viewing_team()].team_name(); } } recorder.speak(cfg); add_chat_message(time(NULL), cfg["id"], side, message, private_message ? events::chat_handler::MESSAGE_PRIVATE : events::chat_handler::MESSAGE_PUBLIC); } void menu_handler::do_search(const std::string& new_search) { if(new_search.empty() == false && new_search != last_search_) last_search_ = new_search; if(last_search_.empty()) return; bool found = false; map_location loc = last_search_hit_; //If this is a location search, just center on that location. std::vector args = utils::split(last_search_, ','); if(args.size() == 2) { int x, y; x = lexical_cast_default(args[0], 0)-1; y = lexical_cast_default(args[1], 0)-1; if(x >= 0 && x < map_.w() && y >= 0 && y < map_.h()) { loc = map_location(x,y); found = true; } } //Start scanning the game map if(loc.valid() == false) loc = map_location(map_.w()-1,map_.h()-1); map_location start = loc; while (!found) { //Move to the next location loc.x = (loc.x + 1) % map_.w(); if(loc.x == 0) loc.y = (loc.y + 1) % map_.h(); //Search label if (!gui_->shrouded(loc)) { const terrain_label* label = gui_->labels().get_label(loc); if(label) { if(std::search(label->text().begin(), label->text().end(), last_search_.begin(), last_search_.end(), chars_equal_insensitive) != label->text().end()) { found = true; } } } //Search unit name if (!gui_->fogged(loc)) { unit_map::const_iterator ui = units_.find(loc); if(ui != units_.end()) { const std::string name = ui->second.name(); if(std::search(name.begin(), name.end(), last_search_.begin(), last_search_.end(), chars_equal_insensitive) != name.end()) { if (!teams_[gui_->viewing_team()].is_enemy(ui->second.side()) || !ui->second.invisible(ui->first, units_,teams_)) { found = true; } } } } if(loc == start) break; } if(found) { last_search_hit_ = loc; gui_->scroll_to_tile(loc,game_display::ONSCREEN,false); gui_->highlight_hex(loc); } else { last_search_hit_ = map_location(); //Not found, inform the player utils::string_map symbols; symbols["search"] = last_search_; const std::string msg = vgettext("Couldn't find label or unit " "containing the string '$search'.", symbols); gui::dialog(*gui_,"",msg).show(); } } void menu_handler::do_command(const std::string& str, int side_num, mouse_handler &mousehandler) { console_handler ch(*this, mousehandler, side_num); ch.dispatch(str); } void console_handler::do_refresh() { image::flush_cache(); menu_handler_.gui_->redraw_everything(); } void console_handler::do_droid() { // :droid [ [on/off]] const std::string side_s = get_arg(1); const std::string action = get_arg(2); // default to the current side if empty const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default(side_s); if (side < 1 || side > menu_handler_.teams_.size()) { utils::string_map symbols; symbols["side"] = side_s; command_failed(vgettext("Can't droid invalid side: '$side'.", symbols)); return; } else if (menu_handler_.teams_[side - 1].is_network()) { utils::string_map symbols; symbols["side"] = lexical_cast(side); command_failed(vgettext("Can't droid networked side: '$side'.", symbols)); return; } else if (menu_handler_.teams_[side - 1].is_human() && action != " off") { //this is our side, so give it to AI menu_handler_.teams_[side - 1].make_human_ai(); menu_handler_.change_controller(lexical_cast(side),"human_ai"); if(team_num_ == side) { //if it is our turn at the moment, we have to indicate to the //play_controller, that we are no longer in control throw end_turn_exception(side); } } else if (menu_handler_.teams_[side - 1].is_ai() && action != " on") { menu_handler_.teams_[side - 1].make_human(); menu_handler_.change_controller(lexical_cast(side),"human"); } menu_handler_.textbox_info_.close(*menu_handler_.gui_); } void console_handler::do_theme() { preferences::show_theme_dialog(*menu_handler_.gui_); } void console_handler::do_control() { // :control if (network::nconnections() == 0) return; const std::string side = get_arg(1); const std::string player = get_arg(2); if(player.empty()) { command_failed_need_arg(2); return; } unsigned int side_num; try { side_num = lexical_cast(side); } catch(bad_lexical_cast&) { utils::string_map symbols; symbols["side"] = side; command_failed(vgettext("Can't change control of invalid side: '$side'.", symbols)); return; } if (side_num < 1 || side_num > menu_handler_.teams_.size()) { utils::string_map symbols; symbols["side"] = side; command_failed(vgettext("Can't change control of out-of-bounds side: '$side'.", symbols)); return; } //if this is our side we are always allowed to change the controller if (menu_handler_.teams_[side_num - 1].is_human()) { if (player == preferences::login()) return; menu_handler_.change_side_controller(side,player,true); } else { //it is not our side, the server will decide if we can change the //controller (that is if we are host of the game) menu_handler_.change_side_controller(side,player); } menu_handler_.textbox_info_.close(*(menu_handler_.gui_)); } void console_handler::do_clear() { menu_handler_.gui_->clear_chat_messages(); } void console_handler::do_sunset() { int delay = lexical_cast_default(get_data()); menu_handler_.gui_->sunset(delay); gui2::twindow::set_sunset(delay); } void console_handler::do_fps() { preferences::set_show_fps(!preferences::show_fps()); } void console_handler::do_benchmark() { menu_handler_.gui_->toggle_benchmark(); } void console_handler::do_save() { savegame save(menu_handler_.gamestate_, preferences::compress_saves()); save.save_game_automatic(menu_handler_.gui_->video(), true, get_data()); } void console_handler::do_save_quit() { savegame save(menu_handler_.gamestate_, preferences::compress_saves()); save.save_game_automatic(menu_handler_.gui_->video(), true, get_data()); throw end_level_exception(QUIT); } void console_handler::do_quit() { throw end_level_exception(QUIT); } void console_handler::do_ignore_replay_errors() { game_config::ignore_replay_errors = (get_data() != "off") ? true : false; } void console_handler::do_nosaves() { game_config::disable_autosave = (get_data() != "off") ? true : false; } void console_handler::do_next_level() { if (!get_data().empty()) menu_handler_.gamestate_.classification().next_scenario = get_data(); throw end_level_exception(VICTORY, "", 100, false, false, false, true, false); } void console_handler::do_choose_level() { std::vector options; int next = 0, nb = 0; foreach (const config &sc, menu_handler_.game_config_.child_range("scenario")) { const std::string &id = sc["id"]; options.push_back(id); if (id == menu_handler_.gamestate_.classification().next_scenario) next = nb; ++nb; } int choice = 0; { gui::dialog menu(*menu_handler_.gui_, _("Choose Scenario (Debug!)"), "", gui::OK_CANCEL); menu.set_menu(options); menu.get_menu().move_selection(next); choice = menu.show(); } if (size_t(choice) < options.size()) { menu_handler_.gamestate_.classification().next_scenario = options[choice]; throw end_level_exception(VICTORY, "", 100, false, false, false, true, false); } } void console_handler::do_debug() { if (network::nconnections() == 0) { print(get_cmd(), _("Debug mode activated!")); game_config::debug = true; } else { command_failed(_("Debug mode not available in network games")); } } void console_handler::do_nodebug() { if (game_config::debug) { print(get_cmd(), _("Debug mode deactivated!")); game_config::debug = false; } } void console_handler::do_lua() { resources::lua_kernel->run(get_data().c_str()); } void console_handler::do_custom() { preferences::set("custom_command", get_data()); } void console_handler::do_set_alias() { const std::string data = get_data(); const std::string::const_iterator j = std::find(data.begin(),data.end(),'='); const std::string alias(data.begin(),j); if(j != data.end()) { const std::string command(j+1,data.end()); if (!command.empty()) { register_alias(command, alias); } else { // "alias something=" deactivate this alias. We just set it // equal to itself here. Later preferences will filter empty alias. register_alias(alias, alias); } preferences::add_alias(alias, command); // directly save it for the moment, but will slow commands sequence preferences::write_preferences(); } else { // "alias something" display its value // if no alias, will be "'something' = 'something'" const std::string command = chmap::get_actual_cmd(alias); print(get_cmd(), "'"+alias+"'" + " = " + "'"+command+"'"); } } void console_handler::do_set_var() { const std::string data = get_data(); if (data.empty()) { command_failed_need_arg(1); return; } const std::string::const_iterator j = std::find(data.begin(),data.end(),'='); if(j != data.end()) { const std::string name(data.begin(),j); const std::string value(j+1,data.end()); menu_handler_.gamestate_.set_variable(name,value); } else { command_failed("Variable not found"); } } void console_handler::do_show_var() { gui2::show_transient_message((*menu_handler_.gui_).video(),"",menu_handler_.gamestate_.get_variable(get_data())); } void console_handler::do_unit() { // prevent SIGSEGV due to attempt to set HP during a fight if (events::commands_disabled > 0) return; const unit_map::iterator i = menu_handler_.current_unit(mouse_handler_); if (i == menu_handler_.units_.end()) return; const std::string data = get_data(1); std::vector parameters = utils::split(data, '=', utils::STRIP_SPACES); if (parameters.size() < 2) return; const std::string& name = parameters[0]; const std::string& value = parameters[1]; // FIXME: Avoids a core dump on display // because alignment strings get reduced // to an enum, then used to index an // array of strings. // But someday the code ought to be // changed to allow general string // alignments for UMC. if (name == "alignment" && (value != "lawful" && value != "neutral" && value != "chaotic")) { command_failed("Invalid alignment: '" + value + "', needs to be one of lawful, neutral or chaotic."); return; } if (name == "advances" ){ int int_value = lexical_cast(value); for (int levels=0; levelssecond.get_experience( i->second.max_experience() - i->second.experience() ); dialogs::advance_unit(menu_handler_.map_, menu_handler_.units_,i->first,*menu_handler_.gui_); } } else { config cfg; i->second.write(cfg); cfg[name] = value; i->second.read(cfg); } menu_handler_.gui_->invalidate(i->first); menu_handler_.gui_->invalidate_unit(); } /*void console_handler::do_buff() { print(get_cmd(), _("Debug mode activated!")); const unit_map::iterator i = menu_handler_.current_unit(mouse_handler_); if(i != menu_handler_.units_.end()) { //i->second.add_trait(get_data()); menu_handler_.gui_->invalidate(i->first); menu_handler_.gui_->invalidate_unit(); } else { command_failed("No unit selected"); } } void console_handler::do_unbuff() { const unit_map::iterator i = menu_handler_.current_unit(mouse_handler_); if(i != menu_handler_.units_.end()) { // FIXME: 'data_' is the trait. Clear it. menu_handler_.gui_->invalidate(i->first); menu_handler_.gui_->invalidate_unit(); } else { command_failed("No unit selected"); } }*/ void console_handler::do_discover() { unit_type_data::unit_type_map::const_iterator i = unit_type_data::types().begin(); for(; i != unit_type_data::types().end(); ++i) { preferences::encountered_units().insert(i->second.id()); } } void console_handler::do_undiscover() { preferences::encountered_units().clear(); } void console_handler::do_create() { const map_location &loc = mouse_handler_.get_last_hex(); if (menu_handler_.map_.on_board(loc)) { const unit_type_data::unit_type_map::const_iterator i = unit_type_data::types().find_unit_type(get_data()); if(i == unit_type_data::types().end()) { command_failed("Invalid unit type"); return; } menu_handler_.units_.erase(loc); menu_handler_.units_.add(loc, unit(&menu_handler_.units_, &i->second, 1, false)); menu_handler_.gui_->invalidate(loc); menu_handler_.gui_->invalidate_unit(); } else { command_failed("Invalid location"); } } void console_handler::do_fog() { menu_handler_.teams_[team_num_ - 1].set_fog( !menu_handler_.teams_[team_num_ - 1].uses_fog() ); recalculate_fog(menu_handler_.map_,menu_handler_.units_,menu_handler_.teams_, team_num_ - 1); menu_handler_.gui_->recalculate_minimap(); menu_handler_.gui_->redraw_everything(); } void console_handler::do_shroud() { menu_handler_.teams_[team_num_ - 1].set_shroud( !menu_handler_.teams_[team_num_ - 1].uses_shroud() ); menu_handler_.clear_shroud(team_num_); menu_handler_.gui_->recalculate_minimap(); menu_handler_.gui_->redraw_everything(); } void console_handler::do_gold() { menu_handler_.teams_[team_num_ - 1].spend_gold(-lexical_cast_default(get_data(),1000)); menu_handler_.gui_->redraw_everything(); } void console_handler::do_event() { game_events::fire(get_data()); menu_handler_.gui_->redraw_everything(); } void console_handler::do_toggle_draw_coordinates() { menu_handler_.gui_->set_draw_coordinates(!menu_handler_.gui_->get_draw_coordinates()); menu_handler_.gui_->invalidate_all(); } void console_handler::do_toggle_draw_terrain_codes() { menu_handler_.gui_->set_draw_terrain_codes(!menu_handler_.gui_->get_draw_terrain_codes()); menu_handler_.gui_->invalidate_all(); } void menu_handler::do_ai_formula(const std::string& str, int side_num, mouse_handler& /*mousehandler*/) { try { add_chat_message(time(NULL), _("ai"), 0, ai::manager::evaluate_command(side_num, str)); } catch(...) { //add_chat_message(time(NULL), _("ai"), 0, "ERROR IN FORMULA"); } } void menu_handler::user_command() { textbox_info_.show(gui::TEXTBOX_COMMAND,sgettext("prompt^Command:"), "", false, *gui_); } void menu_handler::custom_command(mouse_handler &mousehandler, int side_num) { std::vector commands = utils::split(preferences::custom_command(), ';'); std::vector::iterator c = commands.begin(); for (; c != commands.end() ; ++c) { do_command(*c, side_num, mousehandler); } } void menu_handler::ai_formula() { if (network::nconnections() == 0) { std::cerr << "showing ai formula...\n"; textbox_info_.show(gui::TEXTBOX_AI,sgettext("prompt^Command:"), "", false, *gui_); } } void menu_handler::clear_messages() { gui_->clear_chat_messages(); // also clear debug-messages and WML-error-messages } #ifdef USRCMD2 // not used yet - for future hotkey-commands: void menu_handler::user_command_2() { gui2::show_transient_message(*gui_.video(), "Test", "User-Command#2"); //sound::play_bell(game_config::sounds::turn_bell); sound::play_bell("bell.wav"); } void menu_handler::user_command_3() { gui2::show_transient_message(*gui_.video(), "Info", _("User-Command#3")); //gui2::show_error_message(disp().video(), "User-Command#3"); //sound::play_sound("heal.wav"); sound::play_sound("select.wav"); } #endif void menu_handler::change_controller(const std::string& side, const std::string& controller) { config cfg; config& change = cfg.add_child("change_controller"); change["side"] = side; change["controller"] = controller; network::send_data(cfg, 0, true); } void menu_handler::change_side_controller(const std::string& side, const std::string& player, bool own_side) { config cfg; config& change = cfg.add_child("change_controller"); change["side"] = side; change["player"] = player; if(own_side) { change["own_side"] = "yes"; } network::send_data(cfg, 0, true); } } // end namespace events