mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-07 08:59:07 +00:00

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).
1202 lines
34 KiB
C++
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
|