wesnoth/src/dialogs.cpp
J. Tyne cf3c6891f6 Refactor weapon special contexts.
This allows some simplification both when the context is set and (in
one spot) when the context is used.

The semantics for the location parameters to set_specials_context() have
changed; attempts to use the old version will give a compiler error as the
function signatures have also changed (all current calls have been updated).
2012-09-25 19:07:32 +00:00

1202 lines
34 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 - 2012 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
* Various dialogs: advance_unit, show_objectives, save+load game, network::connection.
*/
#include "global.hpp"
#include "actions/attack.hpp"
#include "dialogs.hpp"
#include "game_events.hpp"
#include "game_display.hpp"
#include "game_preferences.hpp"
#include "gui/dialogs/game_delete.hpp"
#include "gettext.hpp"
#include "help.hpp"
#include "language.hpp"
#include "log.hpp"
#include "map.hpp"
#include "map_exception.hpp"
#include "marked-up_text.hpp"
#include "menu_events.hpp"
#include "mouse_handler_base.hpp"
#include "minimap.hpp"
#include "replay.hpp"
#include "resources.hpp"
#include "savegame.hpp"
#include "thread.hpp"
#include "unit_helper.hpp"
#include "unit_types.hpp"
#include "wml_separators.hpp"
#include "widgets/progressbar.hpp"
#include "wml_exception.hpp"
#include "formula_string_utils.hpp"
#include "gui/dialogs/game_save.hpp"
#include "gui/dialogs/transient_message.hpp"
#include <boost/foreach.hpp>
//#ifdef _WIN32
//#include "locale.h"
//#endif
#include <clocale>
static lg::log_domain log_engine("engine");
#define LOG_NG LOG_STREAM(info, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
static lg::log_domain log_display("display");
#define LOG_DP LOG_STREAM(info, log_display)
#define ERR_G LOG_STREAM(err, lg::general)
static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
namespace dialogs
{
int advance_unit_dialog(const map_location &loc)
{
unit_map::iterator u = resources::units->find(loc);
const std::vector<std::string>& options = u->advances_to();
std::vector<std::string> lang_options;
std::vector<unit> sample_units;
for(std::vector<std::string>::const_iterator op = options.begin(); op != options.end(); ++op) {
sample_units.push_back(::get_advanced_unit(*u, *op));
const unit& type = sample_units.back();
#ifdef LOW_MEM
lang_options.push_back(IMAGE_PREFIX
+ static_cast<std::string const &>(type.absolute_image())
+ COLUMN_SEPARATOR + type.type_name());
#else
lang_options.push_back(IMAGE_PREFIX + type.absolute_image() + u->image_mods() + COLUMN_SEPARATOR + type.type_name());
#endif
preferences::encountered_units().insert(*op);
}
bool always_display = false;
BOOST_FOREACH(const config &mod, u->get_modification_advances())
{
if (mod["always_display"].to_bool()) always_display = true;
sample_units.push_back(::get_amla_unit(*u, mod));
const unit& type = sample_units.back();
if (!mod["image"].empty()) {
lang_options.push_back(IMAGE_PREFIX + mod["image"].str() + COLUMN_SEPARATOR + mod["description"].str());
} else {
#ifdef LOW_MEM
lang_options.push_back(IMAGE_PREFIX
+ static_cast<std::string const &>(type.absolute_image())
+ COLUMN_SEPARATOR + mod["description"].str());
#else
lang_options.push_back(IMAGE_PREFIX + type.absolute_image() + u->image_mods() + COLUMN_SEPARATOR + mod["description"].str());
#endif
}
}
assert(!lang_options.empty());
if (lang_options.size() > 1 || always_display)
{
units_list_preview_pane unit_preview(sample_units);
std::vector<gui::preview_pane*> preview_panes;
preview_panes.push_back(&unit_preview);
gui::dialog advances = gui::dialog(*resources::screen,
_("Advance Unit"),
_("What should our victorious unit become?"),
gui::OK_ONLY);
advances.set_menu(lang_options);
advances.set_panes(preview_panes);
return advances.show();
}
return 0;
}
void advance_unit(const map_location &loc, bool random_choice, bool add_replay_event)
{
unit_map::iterator u = resources::units->find(loc);
if(!unit_helper::will_certainly_advance(u)) {
return;
}
LOG_DP << "advance_unit: " << u->type_id() << " (advances: " << u->advances()
<< " XP: " <<u->experience() << '/' << u->max_experience() << ")\n";
int res;
if (random_choice) {
res = rand() % unit_helper::number_of_possible_advances(*u);
} else {
res = advance_unit_dialog(loc);
}
if(add_replay_event) {
recorder.add_advancement(loc);
}
config choice_cfg;
choice_cfg["value"] = res;
recorder.user_input("choose", choice_cfg);
LOG_DP << "animating advancement...\n";
animate_unit_advancement(loc, size_t(res));
// In some rare cases the unit can have enough XP to advance again,
// so try to do that.
// Make sure that we don't enter an infinite level loop.
u = resources::units->find(loc);
if (u != resources::units->end()) {
// Level 10 unit gives 80 XP and the highest mainline is level 5
if (u->experience() < 81) {
// For all leveling up we have to add advancement to replay here because replay
// doesn't handle cascading advancement since it just calls animate_unit_advancement().
advance_unit(loc, random_choice, true);
} else {
ERR_CF << "Unit has too many (" << u->experience()
<< ") XP left; cascade leveling disabled.\n";
}
} else {
ERR_NG << "Unit advanced no longer exists.\n";
}
}
bool animate_unit_advancement(const map_location &loc, size_t choice, const bool &fire_event, const bool animate)
{
const events::command_disabler cmd_disabler;
unit_map::iterator u = resources::units->find(loc);
if (u == resources::units->end()) {
LOG_DP << "animate_unit_advancement suppressed: invalid unit\n";
return false;
} else if (!u->advances()) {
LOG_DP << "animate_unit_advancement suppressed: unit does not advance\n";
return false;
}
const std::vector<std::string>& options = u->advances_to();
std::vector<config> mod_options = u->get_modification_advances();
if(choice >= options.size() + mod_options.size()) {
LOG_DP << "animate_unit_advancement suppressed: invalid option\n";
return false;
}
// When the unit advances, it fades to white, and then switches
// to the new unit, then fades back to the normal color
if (animate && !resources::screen->video().update_locked()) {
unit_animator animator;
bool with_bars = true;
animator.add_animation(&*u,"levelout", u->get_location(), map_location(), 0, with_bars);
animator.start_animations();
animator.wait_for_end();
}
if(choice < options.size()) {
// chosen_unit is not a reference, since the unit may disappear at any moment.
std::string chosen_unit = options[choice];
::advance_unit(loc, chosen_unit, fire_event);
} else {
const config &mod_option = mod_options[choice - options.size()];
::advance_unit(loc, "", fire_event, &mod_option);
}
u = resources::units->find(loc);
resources::screen->invalidate_unit();
if (animate && u != resources::units->end() && !resources::screen->video().update_locked()) {
unit_animator animator;
animator.add_animation(&*u, "levelin", u->get_location(), map_location(), 0, true);
animator.start_animations();
animator.wait_for_end();
animator.set_all_standing();
resources::screen->invalidate(loc);
resources::screen->draw();
events::pump();
}
resources::screen->invalidate_all();
resources::screen->draw();
return true;
}
void show_objectives(const config &level, const std::string &objectives)
{
static const std::string no_objectives(_("No objectives available"));
gui2::show_transient_message(resources::screen->video(), level["name"],
(objectives.empty() ? no_objectives : objectives), "", true);
}
namespace {
/** Class to handle deleting a saved game. */
class delete_save : public gui::dialog_button_action
{
public:
delete_save(display& disp, gui::filter_textbox& filter, std::vector<savegame::save_info>& saves) :
disp_(disp), saves_(saves), filter_(filter) {}
private:
gui::dialog_button_action::RESULT button_pressed(int menu_selection);
display& disp_;
std::vector<savegame::save_info>& saves_;
gui::filter_textbox& filter_;
};
gui::dialog_button_action::RESULT delete_save::button_pressed(int menu_selection)
{
const size_t index = size_t(filter_.get_index(menu_selection));
if(index < saves_.size()) {
// See if we should ask the user for deletion confirmation
if(preferences::ask_delete_saves()) {
if(!gui2::tgame_delete::execute(disp_.video())) {
return gui::CONTINUE_DIALOG;
}
}
// Remove the item from filter_textbox memory
filter_.delete_item(menu_selection);
// Delete the file
savegame::delete_game(saves_[index].name());
// Remove it from the list of saves
saves_.erase(saves_.begin() + index);
return gui::DELETE_ITEM;
} else {
return gui::CONTINUE_DIALOG;
}
}
static const int save_preview_border = 10;
class save_preview_pane : public gui::preview_pane
{
public:
save_preview_pane(CVideo &video, const config& game_config, gamemap* map,
const std::vector<savegame::save_info>& info,
const gui::filter_textbox& textbox) :
gui::preview_pane(video),
game_config_(&game_config),
map_(map), info_(&info),
index_(0),
map_cache_(),
textbox_(textbox)
{
set_measurements(std::min<int>(200,video.getx()/4),
std::min<int>(400,video.gety() * 4/5));
}
void draw_contents();
void set_selection(int index) {
int res = textbox_.get_index(index);
index_ = (res >= 0) ? res : index_;
set_dirty();
}
bool left_side() const { return true; }
private:
const config* game_config_;
gamemap* map_;
const std::vector<savegame::save_info>* info_;
int index_;
std::map<std::string,surface> map_cache_;
const gui::filter_textbox& textbox_;
};
void save_preview_pane::draw_contents()
{
if(size_t(index_) >= info_->size()) {
return;
}
surface screen = video().getSurface();
SDL_Rect const &loc = location();
const SDL_Rect area = create_rect(loc.x + save_preview_border
, loc.y + save_preview_border
, loc.w - save_preview_border * 2
, loc.h - save_preview_border * 2);
const clip_rect_setter clipper(screen, &area);
int ypos = area.y;
bool have_leader_image = false;
const config& summary = ((*info_)[index_]).summary();
const std::string& leader_image = summary["leader_image"].str();
if(!leader_image.empty() && image::exists(leader_image))
{
#ifdef LOW_MEM
const surface& image(image::get_image(leader_image));
#else
// NOTE: assuming magenta for TC here. This is what's used in all of
// mainline, so the compromise should be good enough until we add more
// summary fields to help with this and deciding the side color range.
const surface& image(image::get_image(leader_image + "~RC(magenta>1)"));
#endif
have_leader_image = !image.null();
if(have_leader_image) {
SDL_Rect image_rect = create_rect(area.x, area.y, image->w, image->h);
ypos += image_rect.h + save_preview_border;
sdl_blit(image,NULL,screen,&image_rect);
}
}
std::string map_data = summary["map_data"];
if(map_data.empty()) {
const config &scenario = game_config_->find_child(summary["campaign_type"], "id", summary["scenario"]);
if (scenario && !scenario.find_child("side", "shroud", "yes")) {
map_data = scenario["map_data"].str();
if (map_data.empty() && scenario.has_attribute("map")) {
try {
map_data = read_map(scenario["map"]);
} catch(io_exception& e) {
ERR_G << "could not read map '" << scenario["map"] << "': " << e.what() << '\n';
}
}
}
}
surface map_surf(NULL);
if(map_data.empty() == false) {
const std::map<std::string,surface>::const_iterator itor = map_cache_.find(map_data);
if(itor != map_cache_.end()) {
map_surf = itor->second;
} else if(map_ != NULL) {
try {
const int minimap_size = 100;
map_->read(map_data);
map_surf = image::getMinimap(minimap_size, minimap_size, *map_);
if(map_surf != NULL) {
map_cache_.insert(std::pair<std::string,surface>(map_data, map_surf));
}
} catch(incorrect_map_format_error& e) {
ERR_CF << "map could not be loaded: " << e.message << '\n';
} catch(twml_exception& e) {
ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
}
}
}
if(map_surf != NULL) {
// Align the map to the left when the leader image is missing.
const int map_x = have_leader_image ? area.x + area.w - map_surf->w : area.x;
SDL_Rect map_rect = create_rect(map_x
, area.y
, map_surf->w
, map_surf->h);
ypos = std::max<int>(ypos,map_rect.y + map_rect.h + save_preview_border);
sdl_blit(map_surf,NULL,screen,&map_rect);
}
char time_buf[256] = {0};
const savegame::save_info& save = (*info_)[index_];
tm* tm_l = localtime(&save.modified());
if (tm_l) {
const size_t res = strftime(time_buf,sizeof(time_buf),
(preferences::use_twelve_hour_clock_format() ? _("%a %b %d %I:%M %p %Y") : _("%a %b %d %H:%M %Y")),
tm_l);
if(res == 0) {
time_buf[0] = 0;
}
} else {
LOG_NG << "localtime() returned null for time " << save.modified() << ", save " << save.name();
}
std::stringstream str;
str << font::BOLD_TEXT << font::NULL_MARKUP
<< (*info_)[index_].name() << '\n' << time_buf;
const std::string& campaign_type = summary["campaign_type"];
if (summary["corrupt"].to_bool()) {
str << "\n" << _("#(Invalid)");
} else if (!campaign_type.empty()) {
str << "\n";
if(campaign_type == "scenario") {
const std::string campaign_id = summary["campaign"];
const config *campaign = NULL;
if (!campaign_id.empty()) {
if (const config &c = game_config_->find_child("campaign", "id", campaign_id))
campaign = &c;
}
utils::string_map symbols;
if (campaign != NULL) {
symbols["campaign_name"] = (*campaign)["name"];
} else {
// Fallback to nontranslatable campaign id.
symbols["campaign_name"] = "(" + campaign_id + ")";
}
str << vgettext("Campaign: $campaign_name", symbols);
// Display internal id for debug purposes if we didn't above
if (game_config::debug && (campaign != NULL)) {
str << '\n' << "(" << campaign_id << ")";
}
} else if(campaign_type == "multiplayer") {
str << _("Multiplayer");
} else if(campaign_type == "tutorial") {
str << _("Tutorial");
} else if(campaign_type == "test") {
str << _("Test scenario");
} else {
str << campaign_type;
}
str << "\n";
if (summary["replay"].to_bool() && !summary["snapshot"].to_bool(true)) {
str << _("Replay");
} else if (!summary["turn"].empty()) {
str << _("Turn") << " " << summary["turn"];
} else {
str << _("Scenario start");
}
str << "\n" << _("Difficulty: ") << string_table[summary["difficulty"]];
if(!summary["version"].empty()) {
str << "\n" << _("Version: ") << summary["version"];
}
}
font::draw_text(&video(), area, font::SIZE_SMALL, font::NORMAL_COLOR, str.str(), area.x, ypos, true);
}
std::string format_time_summary(time_t t)
{
time_t curtime = time(NULL);
const struct tm* timeptr = localtime(&curtime);
if(timeptr == NULL) {
return "";
}
const struct tm current_time = *timeptr;
timeptr = localtime(&t);
if(timeptr == NULL) {
return "";
}
const struct tm save_time = *timeptr;
const char* format_string = _("%b %d %y");
if(current_time.tm_year == save_time.tm_year) {
const int days_apart = current_time.tm_yday - save_time.tm_yday;
if(days_apart == 0) {
// save is from today
if(preferences::use_twelve_hour_clock_format() == false) {
format_string = _("%H:%M");
}
else {
format_string = _("%I:%M %p");
}
} else if(days_apart > 0 && days_apart <= current_time.tm_wday) {
// save is from this week
if(preferences::use_twelve_hour_clock_format() == false) {
format_string = _("%A, %H:%M");
}
else {
format_string = _("%A, %I:%M %p");
}
} else {
// save is from current year
format_string = _("%b %d");
}
} else {
// save is from a different year
format_string = _("%b %d %y");
}
char buf[40];
const size_t res = strftime(buf,sizeof(buf),format_string,&save_time);
if(res == 0) {
buf[0] = 0;
}
return buf;
}
} // end anon namespace
std::string load_game_dialog(display& disp, const config& game_config, bool* select_difficulty, bool* show_replay, bool* cancel_orders)
{
std::vector<savegame::save_info> games;
{
cursor::setter cur(cursor::WAIT);
games = savegame::get_saves_list();
}
if(games.empty()) {
gui2::show_transient_message(disp.video(),
_("No Saved Games"),
_("There are no saved games to load.\n\n(Games are saved automatically when you complete a scenario)"));
return "";
}
const events::event_context context;
std::vector<std::string> items;
std::ostringstream heading;
heading << HEADING_PREFIX << _("Name") << COLUMN_SEPARATOR << _("Date");
items.push_back(heading.str());
std::vector<savegame::save_info>::const_iterator i;
for(i = games.begin(); i != games.end(); ++i) {
std::string name = i->name();
utils::truncate_as_wstring(name, std::min<size_t>(name.size(), 40));
std::ostringstream str;
str << name << COLUMN_SEPARATOR << format_time_summary(i->modified());
items.push_back(str.str());
}
gamemap map_obj(game_config, "");
gui::dialog lmenu(disp,
_("Load Game"),
"", gui::NULL_DIALOG);
lmenu.set_basic_behavior(gui::OK_CANCEL);
gui::menu::basic_sorter sorter;
sorter.set_alpha_sort(0).set_id_sort(1);
lmenu.set_menu(items, &sorter);
gui::filter_textbox* filter = new gui::filter_textbox(disp.video(), _("Filter: "), items, items, 1, lmenu);
lmenu.set_textbox(filter);
save_preview_pane save_preview(disp.video(),game_config,&map_obj,games,*filter);
lmenu.add_pane(&save_preview);
// create an option for whether the replay should be shown or not
if(show_replay != NULL) {
lmenu.add_option(_("Show replay"), false,
// use smallgui layout as default, otherwise the load screen glitches at low res!
//game_config::small_gui ? gui::dialog::BUTTON_CHECKBOX : gui::dialog::BUTTON_STANDARD);
gui::dialog::BUTTON_CHECKBOX_LEFT,
_("Play the embedded replay from the saved game if applicable")
);
}
if(cancel_orders != NULL) {
lmenu.add_option(_("Cancel orders"), false,
// use smallgui layout as default, otherwise the load screen glitches at low res!
//game_config::small_gui ? gui::dialog::BUTTON_STANDARD : gui::dialog::BUTTON_EXTRA);
gui::dialog::BUTTON_CHECKBOX_LEFT,
_("Cancel any pending unit movements in the saved game")
);
}
if(select_difficulty != NULL) {
lmenu.add_option(_("Change difficulty"), false,
gui::dialog::BUTTON_CHECKBOX,
_("Change campaign difficulty before loading")
);
}
lmenu.add_button(new gui::standard_dialog_button(disp.video(),_("OK"),0,false), gui::dialog::BUTTON_STANDARD);
lmenu.add_button(new gui::standard_dialog_button(disp.video(),_("Cancel"),1,true), gui::dialog::BUTTON_STANDARD);
delete_save save_deleter(disp,*filter,games);
gui::dialog_button_info delete_button(&save_deleter,_("Delete Save"));
lmenu.add_button(delete_button,
// use smallgui layout as default, otherwise the load screen glitches at low res!
//game_config::small_gui ? gui::dialog::BUTTON_HELP : gui::dialog::BUTTON_EXTRA);
gui::dialog::BUTTON_HELP);
int res = lmenu.show();
if(res == -1)
return "";
res = filter->get_index(res);
int option_index = 0;
if(show_replay != NULL) {
*show_replay = lmenu.option_checked(option_index++);
const config& summary = games[res].summary();
if (summary["replay"].to_bool() && !summary["snapshot"].to_bool(true)) {
*show_replay = true;
}
}
if (cancel_orders != NULL) {
*cancel_orders = lmenu.option_checked(option_index++);
}
if (select_difficulty != NULL) {
*select_difficulty = lmenu.option_checked(option_index++);
}
return games[res].name();
}
namespace {
static const int unit_preview_border = 10;
}
unit_preview_pane::details::details() :
image(),
name(),
type_name(),
race(),
level(0),
alignment(),
traits(),
abilities(),
hitpoints(0),
max_hitpoints(0),
experience(0),
max_experience(0),
hp_color(),
xp_color(),
movement_left(0),
total_movement(0),
attacks()
{
}
unit_preview_pane::unit_preview_pane(const gui::filter_textbox *filter, TYPE type, bool on_left_side) :
gui::preview_pane(resources::screen->video()), index_(0),
details_button_(resources::screen->video(), _("Profile"),
gui::button::TYPE_PRESS,"lite_small", gui::button::MINIMUM_SPACE),
filter_(filter), weapons_(type == SHOW_ALL), left_(on_left_side)
{
unsigned w = font::relative_size(weapons_ ? 200 : 190);
// advance test
unsigned h = font::relative_size(weapons_ ? 440 : 140);
set_measurements(w, h);
}
handler_vector unit_preview_pane::handler_members()
{
handler_vector h;
h.push_back(&details_button_);
return h;
}
bool unit_preview_pane::show_above() const
{
return !weapons_;
}
bool unit_preview_pane::left_side() const
{
return left_;
}
void unit_preview_pane::set_selection(int index)
{
index = std::min<int>(static_cast<int>(size())-1,index);
if (filter_) {
index = filter_->get_index(index);
}
if(index != index_) {
index_ = index;
set_dirty();
if (index >= 0) {
details_button_.set_dirty();
}
}
}
void unit_preview_pane::draw_contents()
{
if(index_ < 0 || index_ >= int(size())) {
return;
}
const details det = get_details();
const bool right_align = left_side();
surface screen = video().getSurface();
SDL_Rect const &loc = location();
const SDL_Rect area = create_rect(loc.x + unit_preview_border
, loc.y + unit_preview_border
, loc.w - unit_preview_border * 2
, loc.h - unit_preview_border * 2);
const clip_rect_setter clipper(screen, &area);
surface unit_image = det.image;
if (!left_)
unit_image = image::reverse_image(unit_image);
SDL_Rect image_rect = create_rect(area.x, area.y, 0, 0);
if(unit_image != NULL) {
SDL_Rect rect = create_rect(
right_align
? area.x
: area.x + area.w - unit_image->w
, area.y
, unit_image->w
, unit_image->h);
sdl_blit(unit_image,NULL,screen,&rect);
image_rect = rect;
}
// Place the 'unit profile' button
const SDL_Rect button_loc = create_rect(
right_align
? area.x
: area.x + area.w - details_button_.location().w
, image_rect.y + image_rect.h
, details_button_.location().w
, details_button_.location().h);
details_button_.set_location(button_loc);
SDL_Rect description_rect = create_rect(image_rect.x
, image_rect.y + image_rect.h + details_button_.location().h
, 0
, 0);
if(det.name.empty() == false) {
std::stringstream desc;
desc << font::BOLD_TEXT << det.name;
const std::string description = desc.str();
description_rect = font::text_area(description, font::SIZE_NORMAL);
description_rect = font::draw_text(&video(), area,
font::SIZE_NORMAL, font::NORMAL_COLOR,
desc.str(), right_align ? image_rect.x :
image_rect.x + image_rect.w - description_rect.w,
image_rect.y + image_rect.h + details_button_.location().h);
}
std::stringstream text;
text << font::unit_type << det.type_name << "\n"
<< font::race
<< (right_align && !weapons_ ? det.race+" " : " "+det.race) << "\n"
<< _("level") << " " << det.level << "\n"
<< det.alignment << "\n"
<< det.traits << "\n";
for(std::vector<t_string>::const_iterator a = det.abilities.begin(); a != det.abilities.end(); ++a) {
if(a != det.abilities.begin()) {
text << ", ";
}
text << (*a);
}
text << "\n";
// Use same coloring as in generate_report.cpp:
text << det.hp_color << _("HP: ")
<< det.hitpoints << "/" << det.max_hitpoints << "\n";
text << det.xp_color << _("XP: ")
<< det.experience << "/" << det.max_experience << "\n";
if(weapons_) {
text << _("Moves: ")
<< det.movement_left << "/" << det.total_movement << "\n";
for(std::vector<attack_type>::const_iterator at_it = det.attacks.begin();
at_it != det.attacks.end(); ++at_it) {
// see generate_report() in generate_report.cpp
text << font::weapon
<< at_it->damage()
<< font::weapon_numbers_sep
<< at_it->num_attacks()
<< " " << at_it->name() << "\n";
text << font::weapon_details
<< " " << string_table["range_" + at_it->range()]
<< font::weapon_details_sep
<< string_table["type_" + at_it->type()] << "\n";
std::string accuracy_parry = at_it->accuracy_parry_description();
if(accuracy_parry.empty() == false) {
text << font::weapon_details << " " << accuracy_parry << "\n";
}
std::string special = at_it->weapon_specials(true);
if (!special.empty()) {
text << font::weapon_details << " " << special << "\n";
}
}
}
// we don't remove empty lines, so all fields stay at the same place
const std::vector<std::string> lines = utils::split(text.str(), '\n',
utils::STRIP_SPACES & !utils::REMOVE_EMPTY);
int ypos = area.y;
if(weapons_) {
ypos += image_rect.h + description_rect.h + details_button_.location().h;
}
for(std::vector<std::string>::const_iterator line = lines.begin(); line != lines.end(); ++line) {
int xpos = area.x;
if(right_align && !weapons_) {
const SDL_Rect& line_area = font::text_area(*line,font::SIZE_SMALL);
// right align, but if too long, don't hide line's beginning
if (line_area.w < area.w)
xpos = area.x + area.w - line_area.w;
}
SDL_Rect cur_area = font::draw_text(&video(),location(),font::SIZE_SMALL,font::NORMAL_COLOR,*line,xpos,ypos);
ypos += cur_area.h;
}
}
units_list_preview_pane::units_list_preview_pane(const unit *u, TYPE type, bool on_left_side) :
unit_preview_pane(NULL, type, on_left_side),
units_(1, u)
{
}
units_list_preview_pane::units_list_preview_pane(const std::vector<const unit *> &units,
const gui::filter_textbox* filter, TYPE type, bool on_left_side) :
unit_preview_pane(filter, type, on_left_side),
units_(units)
{
}
units_list_preview_pane::units_list_preview_pane(const std::vector<unit> &units,
const gui::filter_textbox* filter, TYPE type, bool on_left_side) :
unit_preview_pane(filter, type, on_left_side),
units_(units.size())
{
for (unsigned i = 0; i < units.size(); ++i)
units_[i] = &units[i];
}
size_t units_list_preview_pane::size() const
{
return units_.size();
}
const unit_preview_pane::details units_list_preview_pane::get_details() const
{
const unit &u = *units_[index_];
details det;
det.image = u.still_image();
det.name = u.name();
det.type_name = u.type_name();
if(u.race() != NULL) {
det.race = u.race()->name(u.gender());
}
det.level = u.level();
det.alignment = unit_type::alignment_description(u.alignment(), u.gender());
det.traits = utils::join(u.trait_names(), ", ");
//we filter to remove the tooltips (increment by 2)
const std::vector<boost::tuple<t_string,t_string,t_string> > &abilities = u.ability_tooltips(true);
for(std::vector<boost::tuple<t_string,t_string,t_string> >::const_iterator a = abilities.begin();
a != abilities.end(); ++a) {
det.abilities.push_back(a->get<1>());
}
det.hitpoints = u.hitpoints();
det.max_hitpoints = u.max_hitpoints();
det.hp_color = font::color2markup(u.hp_color());
det.experience = u.experience();
det.max_experience = u.max_experience();
det.xp_color = font::color2markup(u.xp_color());
det.movement_left = u.movement_left();
det.total_movement= u.total_movement();
det.attacks = u.attacks();
return det;
}
void units_list_preview_pane::process_event()
{
if (details_button_.pressed() && index_ >= 0 && index_ < int(size())) {
show_unit_description(*units_[index_]);
}
}
unit_types_preview_pane::unit_types_preview_pane(
std::vector<const unit_type*>& unit_types, const gui::filter_textbox* filter,
int side, TYPE type, bool on_left_side)
: unit_preview_pane(filter, type, on_left_side),
unit_types_(&unit_types), side_(side)
{}
size_t unit_types_preview_pane::size() const
{
return (unit_types_!=NULL) ? unit_types_->size() : 0;
}
const unit_types_preview_pane::details unit_types_preview_pane::get_details() const
{
const unit_type* t = (*unit_types_)[index_];
details det;
if (t==NULL)
return det;
//FIXME: There should be a better way to deal with this
unit_types.find(t->id(), unit_type::WITHOUT_ANIMATIONS);
std::string mod = "~RC(" + t->flag_rgb() + ">" + team::get_side_color_index(side_) + ")";
det.image = image::get_image(t->image()+mod);
det.name = "";
det.type_name = t->type_name();
det.level = t->level();
det.alignment = unit_type::alignment_description(t->alignment(), t->genders().front());
if (const unit_race *r = unit_types.find_race(t->race())) {
assert(!t->genders().empty());
det.race = r->name(t->genders().front());
}
//FIXME: This probably must be move into a unit_type function
BOOST_FOREACH(const config &tr, t->possible_traits())
{
if (tr["availability"] != "musthave") continue;
std::string gender_string = (!t->genders().empty() && t->genders().front()== unit_race::FEMALE) ? "female_name" : "male_name";
t_string name = tr[gender_string];
if (name.empty()) {
name = tr["name"];
}
if (!name.empty()) {
if (!det.traits.empty()) {
det.traits += ", ";
}
det.traits += name;
}
}
det.abilities = t->abilities();
det.hitpoints = t->hitpoints();
det.max_hitpoints = t->hitpoints();
det.hp_color = "<33,225,0>"; // from unit::hp_color()
det.experience = 0;
det.max_experience = t->experience_needed();
det.xp_color = "<0,160,225>"; // from unit::xp_color()
// Check if AMLA color is needed
// FIXME: not sure if it's fully accurate (but not very important for unit_type)
// xp_color also need a simpler function for doing this
BOOST_FOREACH(const config &adv, t->modification_advancements())
{
if (!adv["strict_amla"].to_bool() || !t->can_advance()) {
det.xp_color = "<170,0,255>"; // from unit::xp_color()
break;
}
}
det.movement_left = 0;
det.total_movement= t->movement();
det.attacks = t->attacks();
return det;
}
void unit_types_preview_pane::process_event()
{
if (details_button_.pressed() && index_ >= 0 && index_ < int(size())) {
const unit_type* type = (*unit_types_)[index_];
if (type != NULL)
show_unit_description(*type);
}
}
void show_unit_description(const unit &u)
{
const unit_type* t = u.type();
if (t != NULL)
show_unit_description(*t);
else
// can't find type, try open the id page to have feedback and unit error page
help::show_unit_help(*resources::screen, u.type_id());
}
void show_unit_description(const unit_type &t)
{
const std::string &var = t.get_cfg()["variation_name"];
bool hide_help = t.hide_help();
bool use_variation = false;
if (!var.empty()) {
const unit_type *parent = unit_types.find(t.id());
if (hide_help) {
hide_help = parent->hide_help();
} else {
use_variation = true;
}
}
if (use_variation)
help::show_variation_help(*resources::screen, t.id(), var, hide_help);
else
help::show_unit_help(*resources::screen, t.id(), hide_help);
}
static network::connection network_data_dialog(display& disp, const std::string& msg, config& cfg, network::connection connection_num, network::statistics (*get_stats)(network::connection handle))
{
const size_t width = 300;
const size_t height = 80;
const size_t border = 20;
const int left = disp.w()/2 - width/2;
const int top = disp.h()/2 - height/2;
const events::event_context dialog_events_context;
gui::button cancel_button(disp.video(),_("Cancel"));
std::vector<gui::button*> buttons_ptr(1,&cancel_button);
gui::dialog_frame frame(disp.video(), msg, gui::dialog_frame::default_style, true, &buttons_ptr);
SDL_Rect centered_layout = frame.layout(left,top,width,height).interior;
centered_layout.x = disp.w() / 2 - centered_layout.w / 2;
centered_layout.y = disp.h() / 2 - centered_layout.h / 2;
// HACK: otherwise we get an empty useless space in the dialog below the progressbar
centered_layout.h = height;
frame.layout(centered_layout);
frame.draw();
const SDL_Rect progress_rect = create_rect(centered_layout.x + border
, centered_layout.y + border
, centered_layout.w - border * 2
, centered_layout.h - border * 2);
gui::progress_bar progress(disp.video());
progress.set_location(progress_rect);
events::raise_draw_event();
disp.flip();
network::statistics old_stats = get_stats(connection_num);
cfg.clear();
for(;;) {
const network::connection res = network::receive_data(cfg,connection_num,100);
const network::statistics stats = get_stats(connection_num);
if(stats.current_max != 0 && stats != old_stats) {
old_stats = stats;
progress.set_progress_percent((stats.current*100)/stats.current_max);
std::ostringstream stream;
stream << stats.current/1024 << "/" << stats.current_max/1024 << _("KB");
progress.set_text(stream.str());
}
events::raise_draw_event();
disp.flip();
events::pump();
if(res != 0) {
return res;
}
if(cancel_button.pressed()) {
return res;
}
}
}
network::connection network_send_dialog(display& disp, const std::string& msg, config& cfg, network::connection connection_num)
{
return network_data_dialog(disp, msg, cfg, connection_num,
network::get_send_stats);
}
network::connection network_receive_dialog(display& disp, const std::string& msg, config& cfg, network::connection connection_num)
{
return network_data_dialog(disp, msg, cfg, connection_num,
network::get_receive_stats);
}
} // end namespace dialogs
namespace {
class connect_waiter : public threading::waiter
{
public:
connect_waiter(display& disp, gui::button& button) : disp_(disp), button_(button)
{}
ACTION process();
private:
display& disp_;
gui::button& button_;
};
connect_waiter::ACTION connect_waiter::process()
{
events::raise_draw_event();
disp_.flip();
events::pump();
if(button_.pressed()) {
return ABORT;
} else {
return WAIT;
}
}
}
namespace dialogs
{
network::connection network_connect_dialog(display& disp, const std::string& msg, const std::string& hostname, int port)
{
const size_t width = 250;
const size_t height = 20;
const int left = disp.w()/2 - width/2;
const int top = disp.h()/2 - height/2;
const events::event_context dialog_events_context;
gui::button cancel_button(disp.video(),_("Cancel"));
std::vector<gui::button*> buttons_ptr(1,&cancel_button);
gui::dialog_frame frame(disp.video(), msg, gui::dialog_frame::default_style, true, &buttons_ptr);
frame.layout(left,top,width,height);
frame.draw();
events::raise_draw_event();
disp.flip();
connect_waiter waiter(disp,cancel_button);
return network::connect(hostname,port,waiter);
}
} // end namespace dialogs