wesnoth/src/menu_events.cpp
2011-02-12 06:25:47 +00:00

3639 lines
113 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* $Id$ */
/*
Copyright (C) 2006 - 2011 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
wesnoth playturn Copyright (C) 2003 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* 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 "builder.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 "game_preferences.hpp"
#include "gettext.hpp"
#include "gui/dialogs/edit_label.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "gui/dialogs/wml_message.hpp"
#include "gui/dialogs/gamestate_inspector.hpp"
#include "gui/dialogs/data_manage.hpp"
#include "gui/dialogs/simple_item_selector.hpp"
#include "gui/dialogs/unit_create.hpp"
#include "gui/widgets/settings.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 "play_controller.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 "whiteboard/manager.hpp"
#include "widgets/combo.hpp"
#include <boost/bind.hpp>
static lg::log_domain log_engine("engine");
#define ERR_NG LOG_STREAM(err, log_engine)
#define LOG_NG LOG_STREAM(info, log_engine)
namespace events{
class delete_recall_unit : public gui::dialog_button_action
{
public:
delete_recall_unit(game_display& disp, gui::filter_textbox& filter, std::vector<unit>& units) : disp_(disp), filter_(filter), units_(units) {}
private:
gui::dialog_button_action::RESULT button_pressed(int menu_selection);
game_display& disp_;
gui::filter_textbox& filter_;
std::vector<unit>& units_;
};
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 = gui2::show_message(disp_.video(), "", message.str(), gui2::tmessage::yes_no_buttons);
if(res == gui2::twindow::CANCEL) {
return gui::CONTINUE_DIALOG;
}
}
// Remove the item from filter_textbox memory
filter_.delete_item(menu_selection);
//add dismissal to the undo stack
resources::undo_stack->push_back(undo_action(u, map_location(), undo_action::DISMISS));
//remove the unit from the recall list
std::vector<unit>::iterator dismissed_unit = std::find_if(units_.begin(), units_.end(), boost::bind(&unit::matches_id, _1, u.id()));
assert(dismissed_unit != units_.end());
recorder.add_disband(dismissed_unit->id());
units_.erase(dismissed_unit);
//clear the redo stack to avoid duplication of dismissals
resources::redo_stack->clear();
return gui::DELETE_ITEM;
} else {
return gui::CONTINUE_DIALOG;
}
}
menu_handler::menu_handler(game_display* gui, unit_map& units, std::vector<team>& teams,
const config& level, const gamemap& map,
const config& game_config, const tod_manager& tod_mng, game_state& gamestate) :
gui_(gui),
units_(units),
teams_(teams),
level_(level),
map_(map),
game_config_(game_config),
tod_manager_(tod_mng),
gamestate_(gamestate),
textbox_info_(),
last_search_(),
last_search_hit_(),
last_recruit_()
{
}
menu_handler::~menu_handler()
{
}
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->name().empty()) {
msg << " (" << leader->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 &current_team = teams_[side_num - 1];
dialogs::show_objectives(level_, current_team.objectives());
current_team.reset_objectives_changed();
}
void menu_handler::show_statistics(int side_num)
{
team &current_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 + // 0
_("Name") + COLUMN_SEPARATOR + // 1
_("Moves") + COLUMN_SEPARATOR + // 2
_("Status") + COLUMN_SEPARATOR + // 3
_("HP") + COLUMN_SEPARATOR + // 4
_("Level^Lvl.") + COLUMN_SEPARATOR + // 5
_("XP") + COLUMN_SEPARATOR + // 6
_("unit list^Traits"); // 7
gui::menu::basic_sorter sorter;
sorter.set_alpha_sort(0).set_alpha_sort(1).set_numeric_sort(2);
sorter.set_alpha_sort(3).set_numeric_sort(4).set_level_sort(5, 6);
sorter.set_xp_sort(6).set_alpha_sort(7);
std::vector<std::string> items;
items.push_back(heading);
std::vector<map_location> locations_list;
std::vector<unit> units_list;
int selected = 0;
for(unit_map::const_iterator i = units_.begin(); i != units_.end(); ++i) {
if (i->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->get_location()) {
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->can_recruit() ) {
row << "<205,173,0>"; // gold3
}
row << i->type_name() << COLUMN_SEPARATOR;
if (i->can_recruit() ) {
row << "<205,173,0>"; // gold3
}
row << i->name() << COLUMN_SEPARATOR;
// display move left (0=red, moved=yellow, not moved=green)
if (i->movement_left() == 0) {
row << font::RED_TEXT;
} else if (i->movement_left() < i->total_movement() ) {
row << "<255,255,0>";
} else {
row << font::GREEN_TEXT;
}
row << i->movement_left() << '/' << i->total_movement() << COLUMN_SEPARATOR;
// show icons if unit is slowed, poisoned, petrified, invisible:
if(i->get_state(unit::STATE_PETRIFIED))
row << IMAGE_PREFIX << "misc/petrified.png" << IMG_TEXT_SEPARATOR;
if(i->get_state(unit::STATE_POISONED))
row << IMAGE_PREFIX << "misc/poisoned.png" << IMG_TEXT_SEPARATOR;
if(i->get_state(unit::STATE_SLOWED))
row << IMAGE_PREFIX << "misc/slowed.png" << IMG_TEXT_SEPARATOR;
if(i->invisible(i->get_location(),false))
row << IMAGE_PREFIX << "misc/invisible.png";
row << COLUMN_SEPARATOR;
// Display HP
// see also unit_preview_pane in dialogs.cpp
row << font::color2markup(i->hp_color());
row << i->hitpoints() << '/' << i->max_hitpoints() << COLUMN_SEPARATOR;
// Show units of level (0=gray, 1 normal, 2 bold, 2+ bold&wbright)
int level = i->level();
if(level < 1) {
row << "<150,150,150>";
} else if(level == 1) {
row << font::NORMAL_TEXT;
} else if(level == 2) {
row << font::BOLD_TEXT;
} else if(level > 2 ) {
row << font::BOLD_TEXT << "<255,255,255>";
}
row << level << COLUMN_SEPARATOR;
// Display XP
row << font::color2markup(i->xp_color());
row << i->experience() << "/";
if (i->can_advance()) {
row << i->max_experience();
} else {
row << "-";
}
row << COLUMN_SEPARATOR;
// TODO: show 'loyal' in green / xxx in red // how to handle translations ??
row << utils::join(i->trait_names(), ", ");
items.push_back(row.str());
locations_list.push_back(i->get_location());
units_list.push_back(*i);
}
{
dialogs::units_list_preview_pane unit_preview(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);
//sort by type name
umenu.get_menu().sort_by(0);
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<bool> &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<bool> &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<std::string> items;
std::vector<bool> 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 colors
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);
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 color
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->absolute_image();
leader_bools.push_back(true);
leader_name = leader->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 << leader->image_mods();
#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;
if(!(viewing_team.uses_fog() || viewing_team.uses_shroud())) {
str << "/" << map_.villages().size();
}
str << 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<std::string> items;
std::vector<bool> 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->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->team_color() << '>'
<< team::get_side_color_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)) {
const int res = gui2::show_message((*gui_).video(), "", _("The map already exists. Do you want to overwrite it?"), gui2::tmessage::yes_no_buttons);
overwrite = res == gui2::twindow::CANCEL ? 1 : 0;
}
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;
}
void menu_handler::recruit(int side_num, const map_location &last_hex)
{
team &current_team = teams_[side_num - 1];
std::vector<const unit_type*> sample_units;
gui_->draw(); //clear the old menu
std::vector<std::string> item_keys;
std::vector<std::string> items;
const std::set<std::string>& recruits = current_team.recruits();
for(std::set<std::string>::const_iterator it = recruits.begin(); it != recruits.end(); ++it) {
const unit_type *type = unit_types.find(*it);
if (!type) {
ERR_NG << "could not find unit '" << *it << "'\n";
return;
}
item_keys.push_back(*it);
char prefix;
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
int wb_gold = resources::whiteboard->get_spent_gold_for(side_num);
//display units that we can't afford to recruit in red
prefix = (type->cost() > current_team.gold() - wb_gold
? font::BAD_TEXT : font::NULL_MARKUP);
} // end planned pathfind map scope
std::stringstream description;
description << font::IMAGE << type->image();
#ifndef LOW_MEM
description << "~RC(" << type->flag_rgb() << '>'
<< team::get_side_color_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;
bool left_side = true;
int gold = current_team.gold();
std::string unit_type = "Elvish Fighter";
recruit_res = gui2::show_recruit_message(left_side, gui_->video(),
sample_units, &unit_type, side_num, gold);
//TODO fix the dialog and enable again
// if(recruit_res != -1) {
if(recruit_res == -1) {
do_recruit(unit_type, 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 &current_team = teams_[side_num - 1];
//search for the unit to be recruited in recruits
int recruit_num = 0;
const std::set<std::string>& recruits = current_team.recruits();
for(std::set<std::string>::const_iterator r = recruits.begin(); ; ++r) {
if (r == recruits.end()) {
return;
}
if (name == *r) {
break;
}
++recruit_num;
}
const unit_type *u_type = unit_types.find(name);
assert(u_type);
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
if (u_type->cost() > current_team.gold() - resources::whiteboard->get_spent_gold_for(side_num)) {
gui2::show_transient_message(gui_->video(), "",
_("You dont have enough gold to recruit that unit"));
return;
}
} // end planned pathfind map scope
last_recruit_ = name;
const events::command_disabler disable_commands;
map_location loc = last_hex;
std::string msg;
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
msg = find_recruit_location(side_num, loc);
} // end planned pathfind map scope
if (!msg.empty()) {
gui2::show_transient_message(gui_->video(), "", msg);
return;
}
if (!resources::whiteboard->save_recruit(name, side_num, loc)) {
//create a unit with traits
recorder.add_recruit(recruit_num, loc);
const unit new_unit(u_type, side_num, true);
place_recruit(new_unit, loc, false, true);
current_team.spend_gold(u_type->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());
resources::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 {
resources::undo_stack->push_back(undo_action(new_unit, loc, undo_action::RECRUIT));
}
gui_->recalculate_minimap();
gui_->invalidate_game_status();
gui_->invalidate_all();
recorder.add_checksum_check(loc);
}
}
void menu_handler::recall(int side_num, const map_location &last_hex)
{
if (level_["disallow_recall"].to_bool()) {
gui2::show_transient_message(gui_->video(),"",_("You are separated from your soldiers and may not recall them"));
return;
}
team &current_team = teams_[side_num - 1];
if(!current_team.persistent()) {
ERR_NG << "cannot recall a unit for side " << side_num
<< ", which has no recall list!\n";
return;
}
std::vector<unit>& recall_list_team = current_team.recall_list();
gui_->draw(); //clear the old menu
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
DBG_WB <<"menu_handler::recall: Contents of wb-modified recall list:\n";
foreach(const unit& unit, recall_list_team)
{
DBG_WB << unit.name() << " [" << unit.id() <<"]\n";
}
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)"));
return;
}
} // end planned pathfind map scope
std::vector<std::string> 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);
sorter.set_level_sort(3,4).set_xp_sort(4).set_alpha_sort(5);
options.push_back(heading.str());
options_to_filter.push_back(options.back());
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
foreach (const unit &u, recall_list_team)
{
std::stringstream option, option_to_filter;
std::string name = u.name();
if (name.empty()) name = "-";
option << IMAGE_PREFIX << u.absolute_image();
#ifndef LOW_MEM
option << "~RC(" << u.team_color() << '>'
<< team::get_side_color_index(side_num) << ')';
#endif
option << COLUMN_SEPARATOR
<< u.type_name() << COLUMN_SEPARATOR
<< name << COLUMN_SEPARATOR;
// Show units of level (0=gray, 1 normal, 2 bold, 2+ bold&wbright)
const int level = u.level();
if(level < 1) {
option << "<150,150,150>";
} else if(level == 1) {
option << font::NORMAL_TEXT;
} else if(level == 2) {
option << font::BOLD_TEXT;
} else if(level > 2 ) {
option << font::BOLD_TEXT << "<255,255,255>";
}
option << level << COLUMN_SEPARATOR;
option << font::color2markup(u.xp_color()) << 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;
foreach (const t_string& trait, u.trait_names()) {
option << trait << '\n';
option_to_filter << " " << trait;
}
#endif
options.push_back(option.str());
options_to_filter.push_back(option_to_filter.str());
}
} // end planned pathfind map scope
int res = 0;
{
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
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);
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(recall_list_team, filter);
rmenu.add_pane(&unit_preview);
//sort by level
static int sort_by = 3;
static bool sort_reversed = false;
if(sort_by >= 0) {
rmenu.get_menu().sort_by(sort_by);
// "reclick" on the sorter to reverse the order
if(sort_reversed) {
rmenu.get_menu().sort_by(sort_by);
}
}
res = rmenu.show();
res = filter->get_index(res);
sort_by = rmenu.get_menu().get_sort_by();
sort_reversed = rmenu.get_menu().get_sort_reversed();
if (res < 0) return;
} // end planned pathfind map scope
}
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
int wb_gold = resources::whiteboard->get_spent_gold_for(side_num);
if (current_team.gold() - wb_gold < current_team.recall_cost()) {
utils::string_map i18n_symbols;
i18n_symbols["cost"] = lexical_cast<std::string>(current_team.recall_cost());
std::string 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",
current_team.recall_cost(), i18n_symbols);
gui2::show_transient_message(gui_->video(), "", msg);
return;
}
} // end planned pathfind map scope
LOG_NG << "recall index: " << res << "\n";
const events::command_disabler disable_commands;
map_location recall_location = last_hex;
std::string err;
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
err = find_recruit_location(side_num, recall_location);
} // end planned pathfind map scope
if(!err.empty()) {
gui2::show_transient_message(gui_->video(), "", err);
return;
}
unit* recalled_unit;
{ wb::scoped_planned_pathfind_map future; //< start planned pathfind map scope
recalled_unit = new unit(recall_list_team[res]);
} // end planned pathfind map scope
if (!resources::whiteboard->save_recall(*recalled_unit, side_num, recall_location)) {
do_recall(*recalled_unit, side_num, recall_location);
}
}
void menu_handler::do_recall(const unit& un, int side_num, const map_location& recall_location)
{
team &current_team = teams_[side_num - 1];
std::vector<unit>& recall_list_team = current_team.recall_list();
wb::unit_comparator_predicate comparator(un);
std::vector<unit>::iterator it = std::find_if(recall_list_team.begin(),
recall_list_team.end(), comparator);
if (it == recall_list_team.end())
{
ERR_NG << "menu_handler::do_recall(): Unit doesn't exist in recall list.\n";
return;
}
recall_list_team.erase(it);
recorder.add_recall(un.id(), recall_location);
place_recruit(un, recall_location, true, true);
statistics::recall_unit(un);
current_team.spend_gold(current_team.recall_cost());
bool shroud_cleared = clear_shroud(side_num);
if (shroud_cleared) {
clear_undo_stack(side_num);
} else {
resources::undo_stack->push_back(undo_action(un, recall_location, undo_action::RECALL));
}
resources::redo_stack->clear();
gui_->invalidate_game_status();
gui_->invalidate_all();
recorder.add_checksum_check(recall_location);
}
void menu_handler::undo(int side_num)
{
if(resources::undo_stack->empty())
return;
const events::command_disabler disable_commands;
team &current_team = teams_[side_num - 1];
undo_action& action = resources::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().push_back(action.affected_unit);
resources::whiteboard->on_gamestate_change();
}
} 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);
statistics::un_recall_unit(un);
current_team.spend_gold(-current_team.recall_cost());
current_team.recall_list().push_back(un);
// invalidate before erasing allow us
// to also do the overlapped hexes
gui_->invalidate(action.recall_loc);
units_.erase(action.recall_loc);
resources::whiteboard->on_gamestate_change();
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);
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);
resources::whiteboard->on_gamestate_change();
gui_->draw();
} else {
// Undo a move action
const int starting_moves = action.starting_moves;
std::vector<map_location> route = action.route;
std::reverse(route.begin(),route.end());
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(), action.original_village_owner + 1);
//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->movement_left();
unit_display::move_unit(route, *u, teams_, true, action.starting_dir);
units_.move(u->get_location(), route.back());
unit::clear_status_caches();
u = units_.find(route.back());
u->set_goto(map_location());
std::swap(u->waypoints(), action.waypoints);
u->set_movement(starting_moves);
u->set_standing();
gui_->invalidate_unit_after_move(route.front(), route.back());
resources::whiteboard->on_gamestate_change();
gui_->draw();
}
gui_->invalidate_unit();
gui_->invalidate_game_status();
resources::redo_stack->push_back(action);
resources::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(resources::redo_stack->empty())
return;
const events::command_disabler disable_commands;
team &current_team = teams_[side_num - 1];
undo_action& action = resources::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.affected_unit.id());
std::vector<unit>::iterator unit_it = std::find_if(current_team.recall_list().begin(),
current_team.recall_list().end(), boost::bind(&unit::matches_id, _1, action.affected_unit.id()));
current_team.recall_list().erase(unit_it);
}
} 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
recorder.add_recall(action.affected_unit.id(), action.recall_loc);
map_location loc = action.recall_loc;
const events::command_disabler disable_commands;
const std::string &msg = find_recruit_location(side_num, loc);
if(msg.empty()) {
unit un = action.affected_unit;
//remove the unit from the recall list
std::vector<unit>::iterator unit_it = std::find_if(current_team.recall_list().begin(),
current_team.recall_list().end(), boost::bind(&unit::matches_id, _1, action.affected_unit.id()));
assert(unit_it != current_team.recall_list().end());
current_team.recall_list().erase(unit_it);
place_recruit(un, loc, true, true);
statistics::recall_unit(un);
current_team.spend_gold(current_team.recall_cost());
gui_->invalidate(loc);
gui_->draw();
recorder.add_checksum_check(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<std::string>& recruits = current_team.recruits();
for(std::set<std::string>::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);
const events::command_disabler disable_commands;
const std::string &msg = find_recruit_location(side_num, loc);
if(msg.empty()) {
const unit new_unit = action.affected_unit;
//unit new_unit(action.affected_unit.type(),team_num_,true);
place_recruit(new_unit, loc, false, true);
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_->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<map_location> route = action.route;
unit_map::iterator u = units_.find(route.front());
if(u == units_.end()) {
assert(false);
return;
}
action.starting_moves = u->movement_left();
unit_display::move_unit(route, *u, teams_);
units_.move(u->get_location(), route.back());
u = units_.find(route.back());
unit::clear_status_caches();
u->set_goto(action.affected_unit.get_goto());
std::swap(u->waypoints(), action.waypoints);
u->set_movement(starting_moves);
u->set_standing();
if(map_.is_village(route.back())) {
get_village(route.back(), u->side());
//MP_COUNTDOWN restore capture bonus
if(action.countdown_time_bonus)
{
current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
}
}
gui_->invalidate_unit_after_move(route.front(), route.back());
gui_->draw();
recorder.add_movement(action.route);
}
gui_->invalidate_unit();
gui_->invalidate_game_status();
resources::undo_stack->push_back(action);
resources::redo_stack->pop_back();
}
bool menu_handler::clear_shroud(int side_num)
{
bool cleared = teams_[side_num - 1].auto_shroud_updates() &&
::clear_shroud(side_num);
return cleared;
}
void menu_handler::clear_undo_stack(int side_num)
{
if (!teams_[side_num - 1].auto_shroud_updates())
apply_shroud_changes(*resources::undo_stack, side_num);
resources::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)
{
wb::scoped_planned_pathfind_map wb_modifiers;
gui_->unhighlight_reach();
// Compute enemy movement positions
for(unit_map::iterator u = units_.begin(); u != units_.end(); ++u) {
bool invisible = u->invisible(u->get_location());
if (teams_[side_num - 1].is_enemy(u->side()) &&
!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible)
{
const unit_movement_resetter move_reset(*u);
const pathfind::paths& path = pathfind::paths(map_,units_,
u->get_location(), teams_, false, true,
teams_[gui_->viewing_team()], 0, false, ignore_units);
gui_->highlight_another_reach(path);
}
}
}
void menu_handler::toggle_shroud_updates(int side_num)
{
team &current_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->side() == side_num) {
units_alive++;
if (unit_can_move(*un)) {
if (!un->has_moved()) {
unmoved_units = true;
}
partmoved_units = true;
}
if (un->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 = gui2::show_message((*gui_).video(), "", _("You have not started your turn yet. Do you really want to end your turn?"), gui2::tmessage::yes_no_buttons);
if(res == gui2::twindow::CANCEL) {
return false;
}
}
// Ask for confirmation if units still have planned moves from the whiteboard
if(!is_observer() && resources::whiteboard->current_side_has_actions()) {
const int res = gui2::show_message((*gui_).video(), "", _("Some units have planned actions left. Do you really want to end your turn?"), gui2::tmessage::yes_no_buttons);
if(res == gui2::twindow::CANCEL) {
return false;
}
}
// Ask for confirmation if units still have movement left
if(preferences::yellow_confirm() && partmoved_units) {
const int res = gui2::show_message((*gui_).video(), "", _("Some units have movement left. Do you really want to end your turn?"), gui2::tmessage::yes_no_buttons);
if(res == gui2::twindow::CANCEL) {
return false;
}
} else if (preferences::green_confirm() && unmoved_units) {
const int res = gui2::show_message((*gui_).video(), "", _("Some units have movement left. Do you really want to end your turn?"), gui2::tmessage::yes_no_buttons);
if(res == gui2::twindow::CANCEL) {
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->get_location(), game_display::WARP);
}
}
void menu_handler::unit_description()
{
const unit_map::const_iterator un = current_unit();
if(un != units_.end()) {
dialogs::show_unit_description(*un);
}
}
void menu_handler::rename_unit()
{
const unit_map::iterator un = current_unit();
if (un == units_.end() || gui_->viewing_side() != un->side())
return;
if (un->unrenamable())
return;
std::string name = un->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->get_location());
un->rename(name);
gui_->invalidate_unit();
}
}
unit_map::iterator menu_handler::current_unit()
{
const mouse_handler& mousehandler = resources::controller->get_mouse_handler_base();
unit_map::iterator res = find_visible_unit(mousehandler.get_last_hex(),
teams_[gui_->viewing_team()]);
if(res != units_.end()) {
return res;
} else {
return find_visible_unit(mousehandler.get_selected_hex(),
teams_[gui_->viewing_team()]);
}
}
void menu_handler::create_unit_2(mouse_handler& mousehandler)
{
assert(gui_ != NULL);
//
// The unit creation dialog makes sure unit types
// are properly cached.
//
gui2::tunit_create create_dlg;
create_dlg.show(gui_->video());
if(create_dlg.no_choice()) {
return;
}
const std::string& ut_id = create_dlg.choice();
const unit_type *utp = unit_types.find(ut_id);
if (!utp) {
ERR_NG << "create unit dialog returned inexistent or unusable unit_type id '" << ut_id << "'\n";
return;
}
const unit_type &ut = *utp;
unit_race::GENDER gender = create_dlg.gender();
// Do not try to set bad genders, may mess up l10n
// FIXME: is this actually necessary?
if(ut.genders().end() == std::find(ut.genders().begin(), ut.genders().end(), gender)) {
gender = ut.genders().front();
}
unit chosen(&ut, 1, true, gender);
chosen.new_turn();
const map_location& loc = mousehandler.get_last_hex();
units_.replace(loc, chosen);
if(map_.is_village(loc)) {
get_village(loc, chosen.side());
}
gui_->invalidate(loc);
gui_->invalidate_unit();
}
void menu_handler::create_unit(mouse_handler& mousehandler)
{
/** @todo reenable after releasing 1.7.4; as-is causes memory corruption */
if(gui2::new_widgets) {
create_unit_2(mousehandler);
return;
}
std::vector<std::string> options;
static int last_selection = -1;
static bool random_gender = false;
std::vector<const unit_type*> unit_choices;
const std::string heading = std::string(1,HEADING_PREFIX) +
_("Race") + COLUMN_SEPARATOR +
_("Type");
options.push_back(heading);
foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types())
{
std::stringstream row;
unit_types.find(i.first, unit_type::HELP_INDEX);
std::string race;
if (const unit_race *r = unit_types.find_race(i.second.race())) {
race = r->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(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 unit_race::GENDER gender = random_gender ? unit_race::NUM_GENDERS : unit_race::MALE;
unit chosen(unit_choices[choice], 1, true, gender);
chosen.new_turn();
const map_location& loc = mousehandler.get_last_hex();
units_.replace(loc, chosen);
unit_display::unit_recruited(loc);
if(map_.is_village(loc)) {
get_village(loc, chosen.side());
}
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, team + 1);
} else {
int side = i->side();
++side;
if(side > team::nteams()) {
side = 1;
}
i->set_side(side);
if(map_.is_village(loc)) {
get_village(loc, side);
}
}
}
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;
}
const terrain_label* old_label = gui_->labels().get_label(loc);
std::string label = old_label ? old_label->text() : "";
gui2::tedit_label d(label, team_only);
d.show(gui_->video());
if(d.get_retval() != gui2::twindow::CANCEL) {
std::string team_name;
SDL_Color color = font::LABEL_COLOR;
label = d.label();
if (d.team_only()) {
team_name = gui_->labels().team_name();
} else {
color = 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.team_only() == (old_team_name == "")) {
const terrain_label* old = gui_->labels().set_label(loc, "", old_team_name, color);
if (old) recorder.add_label(old);
}
const terrain_label* res = gui_->labels().set_label(loc, label, team_name, color);
if (res)
recorder.add_label(res);
}
}
void menu_handler::clear_labels()
{
if (gui_->team_valid()
&& !is_observer())
{
gui_->labels().clear(gui_->current_team_name(), false);
recorder.clear_labels(gui_->current_team_name(), false);
}
}
void menu_handler::continue_move(mouse_handler &mousehandler, int side_num)
{
unit_map::iterator i = current_unit();
if (i == units_.end() || !i->move_interrupted()) {
i = units_.find(mousehandler.get_selected_hex());
if (i == units_.end() || !i->move_interrupted()) return;
}
move_unit_to_loc(i, i->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());
pathfind::marked_route route = mousehandler.get_route(&*ui, target, ui->waypoints(), teams_[side_num - 1]);
if(route.steps.empty())
return;
assert(route.steps.front() == ui->get_location());
gui_->set_route(&route);
move_unit(NULL, route.steps, &recorder, resources::undo_stack, true, NULL, continue_move);
gui_->invalidate_game_status();
}
void menu_handler::execute_gotos(mouse_handler &mousehandler, int side)
{
// we will loop on all gotos and try to fully move a maximum of them,
// but we want to avoid multiple blocking of the same unit,
// so, if possible, it's better to first wait that the blocker move
bool wait_blocker_move = true;
std::set<map_location> fully_moved;
bool change = false;
bool blocked_unit = false;
do {
change = false;
blocked_unit = false;
for(unit_map::iterator ui = units_.begin(); ui != units_.end(); ++ui) {
if (ui->side() != side || ui->movement_left() == 0)
continue;
const map_location &current_loc = ui->get_location();
const map_location &goto_loc = ui->get_goto();
if(goto_loc == current_loc){
ui->set_goto(map_location());
continue;
}
if(!map_.on_board(goto_loc))
continue;
// avoid pathfinding calls for finished units
if(fully_moved.count(current_loc))
continue;
pathfind::marked_route route = mousehandler.get_route(&*ui, goto_loc, ui->waypoints(), teams_[side - 1]);
if(route.steps.size() <= 1) { // invalid path
fully_moved.insert(current_loc);
continue;
}
// look where we will stop this turn (turn_1 waypoint or goto)
map_location next_stop = goto_loc;
pathfind::marked_route::mark_map::const_iterator w = route.marks.begin();
for(; w != route.marks.end(); ++w) {
if (w->second.turns == 1) {
next_stop = w->first;
break;
}
}
if(next_stop == current_loc) {
fully_moved.insert(current_loc);
continue;
}
// we delay each blocked move because some other change
// may open a another not blocked path
if(units_.count(next_stop)) {
blocked_unit = true;
if (wait_blocker_move)
continue;
}
gui_->set_route(&route);
int moves = ::move_unit(NULL, route.steps, &recorder, resources::undo_stack, true, NULL, false);
change = moves > 0;
if (change) {
// something changed, resume waiting blocker (maybe one can move now)
wait_blocker_move = true;
}
}
if(!change && wait_blocker_move) {
// no change when waiting, stop waiting and retry
wait_blocker_move = false;
change = true;
}
} while(change && blocked_unit);
// erase the footsteps after movement
gui_->set_route(NULL);
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->side() == side_num && un->movement_left() >= 0)
{
un->set_hold_position(!un->hold_position());
gui_->invalidate(mousehandler.get_selected_hex());
mousehandler.set_current_paths(pathfind::paths());
gui_->draw();
if (un->hold_position()) {
un->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->side() == side_num && un->movement_left() >= 0)
{
un->set_user_end_turn(!un->user_end_turn());
if (un->hold_position() && !un->user_end_turn()) {
un->set_hold_position(false);
}
gui_->invalidate(mousehandler.get_selected_hex());
mousehandler.set_current_paths(pathfind::paths());
gui_->draw();
if (un->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 information 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()) {
std::string data(str_, args[n]);
return utils::strip(data);
} 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<size_t> 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<X>
//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 Worker>
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<std::string, command> command_map;
typedef std::map<std::string, std::string> command_alias_map;
map_command_handler() : cap_("")
{
}
virtual ~map_command_handler() {}
bool empty() const
{
return command_map_.empty();
}
//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<Worker*>(this)->*(c->handler))();
} else {
print(get_cmd(), _("This command is currently unavailable."));
}
} else if (help_on_unknown_) {
utils::string_map symbols;
symbols["command"] = get_cmd();
symbols["help_command"] = cmd_prefix_ + "help";
print("help", VGETTEXT("Unknown command '$command', try $help_command "
"for a list of available commands.", symbols));
}
}
std::vector<std::string> get_commands_list() const
{
std::vector<std::string> res;
foreach(typename command_map::value_type i, command_map_) {
res.push_back(i.first);
}
return res;
}
protected:
void init_map_default()
{
register_command("help", &map_command_handler<Worker>::help,
_("Available commands list and command-specific help. "
"Use \"help all\" to include currently unavailable commands."),
_("do not translate the 'all'^[all|<command>]"));
}
//derived classes initialize the map overriding this function
virtual void init_map() = 0;
//overridden in derived classes to actually print the messages somwehere
virtual void print(const std::string& title, const std::string& message) = 0;
//should be overridden 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 overridden 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:") + std::string(" ") + message);
}
void command_failed_need_arg(int argn)
{
utils::string_map symbols;
symbols["arg_id"] = lexical_cast<std::string>(argn);
command_failed(VGETTEXT("Missing argument $arg_id", symbols));
}
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 << "; ";
}
}
utils::string_map symbols;
symbols["flags_description"] = get_flags_description();
symbols["list_of_commands"] = ss.str();
symbols["help_command"] = cmd_prefix_ + "help";
print(_("help"), VGETTEXT("Available commands $flags_description:\n$list_of_commands", symbols));
print(_("help"), VGETTEXT("Type $help_command <command> for more info.", symbols));
}
//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<std::string> 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;
}
//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<typename command_map::iterator, bool> 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<std::string> get_aliases(const std::string& cmd)
{
std::vector<std::string> 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 <class Worker>
typename map_command_handler<Worker>::command_map map_command_handler<Worker>::command_map_;
template <class Worker>
typename map_command_handler<Worker>::command_alias_map map_command_handler<Worker>::command_alias_map_;
template <class Worker>
bool map_command_handler<Worker>::help_on_unknown_ = true;
template <class Worker>
bool map_command_handler<Worker>::show_unavailable_ = false;
template <class Worker>
std::string map_command_handler<Worker>::cmd_prefix_;
//command handler for chat /commands
class chat_command_handler : public map_command_handler<chat_command_handler>
{
public:
typedef map_command_handler<chat_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_gen_room_query();
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) - admin command");
}
std::string get_command_flags_description(
const map_command_handler<chat_command_handler>::command& c) const
{
if (c.has_flag('A')) {
return std::string(" ") + _("(admin only)");
} else {
return "";
}
}
bool is_enabled(
const map_command_handler<chat_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_alias("query", "q");
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."), _("<nick>"));
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."), _("<nick>"));
register_command("kick", &chat_command_handler::do_network_send_req_arg,
_("Kick a player or observer."), _("<nick>"));
register_command("mute", &chat_command_handler::do_network_send,
_("Mute an observer. Without an argument displays the mute status."), _("<nick>"));
register_command("unmute", &chat_command_handler::do_network_send,
_("Unmute an observer. Without an argument unmutes everyone."), _("<nick>"));
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."), _("<message>"));
register_alias("emote", "me");
register_command("whisper", &chat_command_handler::do_whisper,
_("Sends a private message. "
"You cant send messages to players that dont control "
"a side in a running game you are in."), _("<nick> <message>"));
register_alias("whisper", "msg");
register_alias("whisper", "m");
register_command("log", &chat_command_handler::do_log,
_("Change the log level of a log domain."), _("<level> <domain>"));
register_command("ignore", &chat_command_handler::do_ignore,
_("Add a nick to your ignores list."), _("<nick>"));
register_command("friend", &chat_command_handler::do_friend,
_("Add a nick to your friends list."), _("<nick>"));
register_command("remove", &chat_command_handler::do_remove,
_("Remove a nick from your ignores or friends list."), _("<nick>"));
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"), _("<password> <email (optional)>"));
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'."),
_("<detail> <value>"));
register_command("info", &chat_command_handler::do_info,
_("Request information about a nick."), _("<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."), _("<room>"));
register_alias("join", "j");
register_command("part", &chat_command_handler::do_network_send_req_arg,
_("Part a room."), _("<room>"));
register_command("names", &chat_command_handler::do_room_query,
_("List room members."), _("<room>"));
register_command("rooms", &chat_command_handler::do_room_query_noarg,
_("List available rooms."));
register_command("room", &chat_command_handler::do_chanmsg,
_("Room message."), _("<room> <msg>"));
register_command("room_query", &chat_command_handler::do_gen_room_query,
_("Room query."), _("<room> <type> [value]"));
register_alias("room_query", "rq");
}
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<console_handler>, private chat_command_handler
{
public:
//convenience typedef
typedef map_command_handler<console_handler> chmap;
console_handler(menu_handler& menu_handler)
: chmap(), chat_command_handler(menu_handler, true), menu_handler_(menu_handler), team_num_(resources::controller->current_side())
{}
using chmap::dispatch; //disambiguate
using chmap::get_commands_list;
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_foreground();
void do_layers();
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_turn();
void do_turn_limit();
void do_debug();
void do_nodebug();
void do_lua();
void do_unsafe_lua();
void do_custom();
void do_set_alias();
void do_set_var();
void do_show_var();
void do_inspect();
void do_manage();
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();
void do_toggle_whiteboard();
std::string get_flags_description() const {
return _("(D) - debug only, (N) - network only, (A) - admin only");
}
using chat_command_handler::get_command_flags_description; //silence a warning
std::string get_command_flags_description(const chmap::command& c) const
{
std::string space(" ");
return std::string(c.has_flag('D') ? space + _("(debug command)") : "")
+ std::string(c.has_flag('N') ? space + _("(network only)") : "")
+ std::string(c.has_flag('A') ? space + _("(admin 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."), _("do not translate the on/off^[<side> [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."), _("<side> <nick>"), "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("foreground", &console_handler::do_foreground,
_("Debug foreground terrain."), "", "D");
register_command("layers", &console_handler::do_layers,
_("Debug layers from terrain under the mouse."), "", "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."));
// Note the next value is used hardcoded in the init tests.
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'"), _("<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("turn", &console_handler::do_turn,
_("Change turn number (and time of day), or increase by one if no number is specified."), _("[turn]"), "D");
register_command("turn_limit", &console_handler::do_turn_limit,
_("Change turn limit, or turn the turn limit off if no number is specified or its 1."), _("[limit]"), "D");
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."), _("<command>[;<command>...]"), "D");
register_command("unsafe_lua", &console_handler::do_unsafe_lua,
_("Grant higher privileges to Lua scripts."), "", "D");
register_command("custom", &console_handler::do_custom,
_("Set the command used by the custom command hotkey"), _("<command>[;<command>...]"));
register_command("inspect", &console_handler::do_inspect,
_("Launch the gamestate inspector"), "", "D");
register_command("manage", &console_handler::do_manage,
_("Manage persistence data"), "", "D");
register_command("alias", &console_handler::do_set_alias,
_("Set or show alias to a command"), _("<name>[=<command>]"));
register_command("set_var", &console_handler::do_set_var,
_("Set a scenario variable."), _("<var>=<value>"), "D");
register_command("show_var", &console_handler::do_show_var,
_("Show a scenario variable."), _("<var>"), "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."), "");
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");
register_command("whiteboard", &console_handler::do_toggle_whiteboard,
_("Toggle planning mode."));
register_alias("whiteboard", "wb");
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_;
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: "@<level@> @<domain@>"
*/
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<std::string>(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);
}
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::user_relation_changed(const std::string& /*name*/)
{
}
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);
}
void chat_handler::add_whisper_sent(const std::string& receiver, const std::string& message)
{
utils::string_map symbols;
symbols["receiver"] = receiver;
add_chat_message(time(NULL), VGETTEXT("whisper to $receiver", symbols), 0, message);
}
void chat_handler::add_whisper_received(const std::string& sender, const std::string& message)
{
utils::string_map symbols;
symbols["sender"] = sender;
add_chat_message(time(NULL), VGETTEXT("whisper: $sender", symbols), 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);
}
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);
}
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_arg(1);
q.add_child(get_cmd());
network::send_data(data, 0);
}
void chat_command_handler::do_gen_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_arg(1);
config& c = q.add_child(get_arg(2));
c["value"] = get_data(3);
network::send_data(data, 0);
}
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<std::string>& tmp = preferences::get_ignores();
print(_("ignores list"), tmp.empty() ? _("(empty)") : utils::join(tmp));
} else {
for(int i = 1; !get_arg(i).empty(); i++){
utils::string_map symbols;
symbols["nick"] = get_arg(i);
if (preferences::add_ignore(get_arg(i))) {
print(_("ignores list"), VGETTEXT("Added to ignore list: $nick", symbols));
chat_handler_.user_relation_changed(get_arg(i));
} else {
command_failed(VGETTEXT("Invalid username: $nick", symbols));
}
}
}
}
void chat_command_handler::do_friend()
{
if (get_arg(1).empty()) {
const std::set<std::string>& tmp = preferences::get_friends();
print(_("friends list"), tmp.empty() ? _("(empty)") : utils::join(tmp));
} else {
for(int i = 1;!get_arg(i).empty();i++){
utils::string_map symbols;
symbols["nick"] = get_arg(i);
if (preferences::add_friend(get_arg(i))) {
chat_handler_.user_relation_changed(get_arg(i));
print(_("friends list"), VGETTEXT("Added to friends list: $nick", symbols));
} else {
command_failed(VGETTEXT("Invalid username: $nick", symbols));
}
}
}
}
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));
chat_handler_.user_relation_changed(get_arg(i));
utils::string_map symbols;
symbols["nick"] = get_arg(i);
print(_("friends and ignores list"), VGETTEXT("Removed from list: $nick", symbols));
}
}
void chat_command_handler::do_display()
{
const std::set<std::string> & friends = preferences::get_friends();
const std::set<std::string> & 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(_("friends and ignores 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 &reg = nickserv.add_child("register");
reg["password"] = get_arg(1);
if(!get_data(2).empty()) {
reg["mail"] = get_arg(2);
}
std::string msg;
if (get_data(2).empty()) {
msg = _("registering with password *** and no email address");
} else {
utils::string_map symbols;
symbols["email"] = get_data(2);
msg = VGETTEXT("registering with password *** and "
"email address $email", symbols);
}
print(_("nick registration"), msg);
network::send_data(data, 0);
}
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);
}
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);
utils::string_map symbols;
symbols["var"] = get_arg(1);
symbols["value"] = get_arg(2);
print(_("nick registration"), VGETTEXT("setting $var to $value", symbols));
network::send_data(data, 0);
}
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);
utils::string_map symbols;
symbols["nick"] = get_arg(1);
print(_("nick registration"), VGETTEXT("requesting information for user $nick", symbols));
network::send_data(data, 0);
}
void chat_command_handler::do_details() {
config data;
config& nickserv = data.add_child("nickserv");
nickserv.add_child("details");
network::send_data(data, 0);
}
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"] = 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<std::string> args = utils::split(last_search_, ',');
if(args.size() == 2) {
int x, y;
x = lexical_cast_default<int>(args[0], 0)-1;
y = lexical_cast_default<int>(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) {
std::string label_text = label->text().str();
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->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->side()) ||
!ui->invisible(ui->get_location())) {
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)
{
console_handler ch(*this);
ch.dispatch(str);
}
std::vector<std::string> menu_handler::get_commands_list()
{
console_handler ch(*this);
return ch.get_commands_list();
}
void console_handler::do_refresh() {
image::flush_cache();
menu_handler_.gui_->redraw_everything();
}
void console_handler::do_droid() {
// :droid [<side> [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<unsigned int>(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<std::string>(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<std::string>(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<std::string>(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 <side> <nick>
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<unsigned int>(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<int>(get_data());
menu_handler_.gui_->sunset(delay);
gui2::twindow::set_sunset(delay);
}
void console_handler::do_foreground() {
menu_handler_.gui_->toggle_debug_foreground();
}
void console_handler::do_layers() {
const mouse_handler& mousehandler = resources::controller->get_mouse_handler_base();
const map_location &loc = mousehandler.get_last_hex();
std::vector<std::string> layers;
//NOTE: columns reflect WML keys, don't translate them
std::string heading = std::string(1,HEADING_PREFIX) +
"^#" + COLUMN_SEPARATOR + // 0
"Image" + COLUMN_SEPARATOR + // 1
"Name" + COLUMN_SEPARATOR + // 2
"Loc" + COLUMN_SEPARATOR + // 3
"Layer" + COLUMN_SEPARATOR + // 4
"Base.x" + COLUMN_SEPARATOR + // 5
"Base.y" + COLUMN_SEPARATOR + // 6
"Center" // 7
;
layers.push_back(heading);
display& disp = *(menu_handler_.gui_);
terrain_builder& builder = disp.get_builder();
terrain_builder::tile* tile = builder.get_tile(loc);
const std::string& tod_id = disp.get_time_of_day(loc).id;
terrain_builder::tile::logs tile_logs;
tile->rebuild_cache(tod_id, &tile_logs);
int order = 1;
foreach(const terrain_builder::tile::log_details det, tile_logs) {
const terrain_builder::tile::rule_image_rand& ri = *det.first;
const terrain_builder::rule_image_variant& variant = *det.second;
///@TODO also use random image variations (not just take 1st)
const image::locator& img = variant.images.front().get_first_frame();
const std::string& name = img.get_filename();
///@TODO deal with (rarely used) ~modifications
//const std::string& modif = img.get_modifications();
const map_location& loc_cut = img.get_loc();
std::ostringstream str;
str << (ri->is_background() ? "B ": "F ") << order
<< COLUMN_SEPARATOR
<< IMAGE_PREFIX << "terrain/foreground.png"
<< "~BLIT("
<< name << "~LOC("
<< loc_cut.x << "," << loc_cut.y << ","
<< img.get_center_x() << "," << img.get_center_y() << ")"
<< ")"
<< COLUMN_SEPARATOR
<< IMAGE_PREFIX << name << "~SCALE(72,72)"
<< IMG_TEXT_SEPARATOR << name
<< COLUMN_SEPARATOR << img.get_loc()
<< COLUMN_SEPARATOR << ri->layer
<< COLUMN_SEPARATOR << ri->basex
<< COLUMN_SEPARATOR << ri->basey
<< COLUMN_SEPARATOR
<< ri->center_x << ", " << ri->center_y;
layers.push_back(str.str());
++order;
}
std::vector<std::string> flags(tile->flags.begin(),tile->flags.end());
std::ostringstream info;
// NOTE using ", " also allows better word wrapping
info << "Flags :" << utils::join(flags, ", ");
{
gui::dialog menu(*menu_handler_.gui_, _("Layers"), info.str(), gui::OK_CANCEL);
menu.set_menu(layers);
menu.show();
}
}
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::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::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();
end_level_data &e = resources::controller->get_end_level_data();
e.carryover_percentage = 100;
e.carryover_add = false;
e.gold_bonus = false;
e.carryover_report = false;
e.prescenario_save = true;
e.linger_mode = false;
throw end_level_exception(VICTORY);
}
void console_handler::do_choose_level() {
std::vector<std::string> 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;
}
// find scenarios of multiplayer campaigns
// (assumes that scenarios are ordered properly in the game_config)
std::string& scenario = menu_handler_.gamestate_.mp_settings().mp_scenario;
foreach (const config &mp, menu_handler_.game_config_.child_range("multiplayer"))
{
if (mp["id"] == scenario)
{
const std::string &id = mp["id"];
options.push_back(id);
if (id == menu_handler_.gamestate_.classification().next_scenario)
next = nb;
++nb;
scenario = mp["next_scenario"].str();
}
}
std::sort(options.begin(), options.end());
int choice = 0;
{
gui2::tsimple_item_selector dlg(_("Choose Scenario (Debug!)"), "", options);
dlg.set_selected_index(next);
dlg.show(menu_handler_.gui_->video());
choice = dlg.selected_index();
}
if(choice == -1)
return;
if (size_t(choice) < options.size()) {
menu_handler_.gamestate_.classification().next_scenario = options[choice];
end_level_data &e = resources::controller->get_end_level_data();
e.carryover_percentage = 100;
e.carryover_add = false;
e.gold_bonus = false;
e.carryover_report = false;
e.prescenario_save = true;
e.linger_mode = false;
throw end_level_exception(VICTORY);
}
}
void console_handler::do_turn()
{
if (!get_data().empty()) {
int turn = lexical_cast_default<int>(get_data(), 1);
resources::tod_manager->set_turn(turn);
} else {
resources::tod_manager->next_turn();
}
menu_handler_.gui_->new_turn();
menu_handler_.gui_->redraw_everything();
}
void console_handler::do_turn_limit()
{
tod_manager& tod_man = *resources::tod_manager;
int limit =
get_data().empty() ? -1 : lexical_cast_default<int>(get_data(), 1);
tod_man.set_number_of_turns(limit);
menu_handler_.gui_->redraw_everything();
}
void console_handler::do_debug() {
if (network::nconnections() == 0 || game_config::mp_debug) {
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());
game_events::commit();
}
void console_handler::do_unsafe_lua()
{
if (gui2::show_message(resources::screen->video(), _("Unsafe Lua scripts."),
_("You are about to open a security breach in Wesnoth. Are you sure you want to continue? If you have downloaded add-ons, do not click 'ok'! They would instantly take over your computer. You have been warned."),
gui2::tmessage::ok_cancel_buttons) == gui2::twindow::OK)
{
print(get_cmd(), _("Unsafe mode enabled!"));
resources::lua_kernel->load_package();
}
}
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_inspect() {
vconfig cfg = vconfig::empty_vconfig();
gui2::tgamestate_inspector inspect_dialog(cfg);
inspect_dialog.show(resources::screen->video());
}
void console_handler::do_manage() {
config cfg;
gui2::tdata_manage manager(cfg);
manager.show(resources::screen->video());
}
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();
if (i == menu_handler_.units_.end()) return;
const std::string data = get_data(1);
std::vector<std::string> 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" && value != "liminal")) {
utils::string_map symbols;
symbols["alignment"] = get_arg(1);
command_failed(VGETTEXT("Invalid alignment: '$alignment',"
" needs to be one of lawful, neutral, chaotic or liminal.", symbols));
return;
}
if (name == "advances" ){
int int_value = lexical_cast<int>(value);
for (int levels=0; levels<int_value; levels++) {
i->set_experience(i->max_experience());
dialogs::advance_unit(i->get_location());
}
} else {
config cfg;
i->write(cfg);
const map_location loc = i->get_location();
menu_handler_.units_.erase(loc);
cfg[name] = value;
unit new_u(cfg, true);
menu_handler_.units_.add(loc, new_u);
}
menu_handler_.gui_->invalidate(i->get_location());
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();
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();
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() {
foreach (const unit_type_data::unit_type_map::value_type &i, unit_types.types()) {
preferences::encountered_units().insert(i.second.id());
}
}
void console_handler::do_undiscover() {
const int res = gui2::show_message((*menu_handler_.gui_).video(), "Undiscover", _("Do you wish to clear all of your discovered units from help?"), gui2::tmessage::yes_no_buttons);
if(res != gui2::twindow::CANCEL) {
preferences::encountered_units().clear();
}
}
void console_handler::do_create() {
const mouse_handler& mousehandler = resources::controller->get_mouse_handler_base();
const map_location &loc = mousehandler.get_last_hex();
if (menu_handler_.map_.on_board(loc)) {
const unit_type *ut = unit_types.find(get_data());
if (!ut) {
command_failed(_("Invalid unit type"));
return;
}
menu_handler_.units_.erase(loc);
unit created(ut, 1, true);
created.new_turn();
menu_handler_.units_.add(loc, created);
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(team_num_);
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<int>(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 console_handler::do_toggle_whiteboard() {
resources::whiteboard->set_active(!resources::whiteboard->is_active());
if (resources::whiteboard->is_active()) {
print(get_cmd(), _("Planning mode activated!"));
resources::whiteboard->print_help_once();
} else {
print(get_cmd(), _("Planning mode deactivated!"));
}
}
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(end_turn_exception&) {
resources::controller->force_end_turn();
} 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()
{
std::vector<std::string> commands = utils::split(preferences::custom_command(), ';');
std::vector<std::string>::iterator c = commands.begin();
for (; c != commands.end() ; ++c) {
do_command(*c);
}
}
void menu_handler::ai_formula()
{
if (network::nconnections() == 0) {
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
}
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);
}
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"] = true;
}
network::send_data(cfg, 0);
}
} // end namespace events