From 1949e00955a51161fe17b60273ebbcc5a2db8885 Mon Sep 17 00:00:00 2001 From: Dave White Date: Thu, 10 Jun 2004 23:43:25 +0000 Subject: [PATCH] added animated on-map items. New attack dialog. New load game dialog --- .../Heir_To_The_Throne/Isle_of_Anduin.cfg | 2 +- .../The_Siege_of_Elensefar.cfg | 2 +- data/translations/english.cfg | 2 + src/ai.cpp | 4 +- src/dialogs.cpp | 531 ++++++++++++++---- src/dialogs.hpp | 31 +- src/display.cpp | 11 +- src/display.hpp | 3 +- src/events.cpp | 12 +- src/events.hpp | 5 +- src/game.cpp | 7 + src/game_events.cpp | 5 +- src/gamestatus.cpp | 4 + src/gamestatus.hpp | 2 + src/map.cpp | 3 - src/playturn.cpp | 198 +++---- src/show_dialog.cpp | 100 ++-- src/show_dialog.hpp | 13 +- 18 files changed, 656 insertions(+), 279 deletions(-) diff --git a/data/scenarios/Heir_To_The_Throne/Isle_of_Anduin.cfg b/data/scenarios/Heir_To_The_Throne/Isle_of_Anduin.cfg index 3edd6cd95ff..4bf4bae2acc 100644 --- a/data/scenarios/Heir_To_The_Throne/Isle_of_Anduin.cfg +++ b/data/scenarios/Heir_To_The_Throne/Isle_of_Anduin.cfg @@ -62,7 +62,7 @@ Defeat: side=2 canrecruit=1 recruit=Orcish Grunt,Wolf Rider,Orcish Archer,Troll Whelp,Saurian - {GOLD 40 120 180} + {GOLD 40 100 180} enemy=1 [ai] recruitment_pattern=scout,scout,fighter,fighter,archer diff --git a/data/scenarios/Heir_To_The_Throne/The_Siege_of_Elensefar.cfg b/data/scenarios/Heir_To_The_Throne/The_Siege_of_Elensefar.cfg index d65a85be951..002f3394ff8 100644 --- a/data/scenarios/Heir_To_The_Throne/The_Siege_of_Elensefar.cfg +++ b/data/scenarios/Heir_To_The_Throne/The_Siege_of_Elensefar.cfg @@ -312,7 +312,7 @@ Defeat: [message] id=msg5_23 description=Delfador - message="During the reign of Garard I, your uncle's father, the dwarves of Knalga agreed to make the king a magnificent scepter. It took their finest smiths years to make it. But soon after it was completed, Orcs invaded the tunnels of Knalga. Now Knalga is in chaos, and, though some Dwarves still live in parts of it, at constant war with the Orcs, the Scepter was lost somewhere in the great caverns." + message="During the reign of Garard I, your grandfather, the dwarves of Knalga agreed to make the king a magnificent scepter. It took their finest smiths years to make it. But soon after it was completed, Orcs invaded the tunnels of Knalga. Now Knalga is in chaos, and, though some Dwarves still live in parts of it, at constant war with the Orcs, the Scepter was lost somewhere in the great caverns." [/message] [message] diff --git a/data/translations/english.cfg b/data/translations/english.cfg index e68782a1c18..0690a426347 100644 --- a/data/translations/english.cfg +++ b/data/translations/english.cfg @@ -630,6 +630,8 @@ see_also="See Also..." attack_type="Attack Type" +attack_enemy="Attack Enemy" + attacks="attacks" damage="damage" hexes="hexes" diff --git a/src/ai.cpp b/src/ai.cpp index aeb9b4ad121..8b5997eee7c 100644 --- a/src/ai.cpp +++ b/src/ai.cpp @@ -762,13 +762,13 @@ void ai_interface::attack_enemy(const location& u, const location& target, int w attack(info_.disp,info_.map,info_.teams,u,target,weapon,info_.units,info_.state,info_.gameinfo,false); check_victory(info_.units,info_.teams); - dialogs::advance_unit(info_.gameinfo,info_.units,u,info_.disp,true); + dialogs::advance_unit(info_.gameinfo,info_.map,info_.units,u,info_.disp,true); const unit_map::const_iterator defender = info_.units.find(target); if(defender != info_.units.end()) { const size_t defender_team = size_t(defender->second.side()) - 1; if(defender_team < info_.teams.size()) { - dialogs::advance_unit(info_.gameinfo,info_.units,target,info_.disp,!info_.teams[defender_team].is_human()); + dialogs::advance_unit(info_.gameinfo,info_.map,info_.units,target,info_.disp,!info_.teams[defender_team].is_human()); } } diff --git a/src/dialogs.cpp b/src/dialogs.cpp index e9f7bea8152..91870a0331e 100644 --- a/src/dialogs.cpp +++ b/src/dialogs.cpp @@ -14,6 +14,7 @@ #include "dialogs.hpp" #include "events.hpp" #include "font.hpp" +#include "game_config.hpp" #include "language.hpp" #include "log.hpp" #include "preferences.hpp" @@ -34,6 +35,7 @@ namespace dialogs { void advance_unit(const game_data& info, + const gamemap& map, std::map& units, gamemap::location loc, display& gui, bool random_choice) @@ -62,9 +64,14 @@ void advance_unit(const game_data& info, res = rand()%options.size(); } else if(options.size() > 1) { + const events::event_context dialog_events_context; + unit_preview_pane unit_preview(gui,&map,sample_units); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + res = gui::show_dialog(gui,NULL,string_table["advance_unit_heading"], string_table["advance_unit_message"], - gui::OK_ONLY, &lang_options, &sample_units); + gui::OK_ONLY, &lang_options, &preview_panes); } recorder.choose_option(res); @@ -156,12 +163,13 @@ namespace { class delete_save : public gui::dialog_button_action { public: - delete_save(display& disp, std::vector& saves) : disp_(disp), saves_(saves) {} + delete_save(display& disp, std::vector& saves, std::vector& save_summaries) : disp_(disp), saves_(saves), summaries_(save_summaries) {} private: gui::dialog_button_action::RESULT button_pressed(int menu_selection); display& disp_; std::vector& saves_; + std::vector& summaries_; }; gui::dialog_button_action::RESULT delete_save::button_pressed(int menu_selection) @@ -193,23 +201,173 @@ gui::dialog_button_action::RESULT delete_save::button_pressed(int menu_selection //remove it from the list of saves saves_.erase(saves_.begin() + index); + + if(index < summaries_.size()) { + summaries_.erase(summaries_.begin() + index); + } + return gui::dialog_button_action::DELETE_ITEM; } else { return gui::dialog_button_action::NO_EFFECT; } } +static const SDL_Rect save_preview_pane_area = {-200,-400,200,400}; +static const int save_preview_border = 10; + +class save_preview_pane : public gui::preview_pane +{ +public: + save_preview_pane(display& disp, const config& game_config, gamemap* map, const game_data& data, + const std::vector& info, const std::vector& summaries) + : gui::preview_pane(disp), game_config_(&game_config), map_(map), data_(&data), info_(&info), summaries_(&summaries), index_(0) + { + set_location(save_preview_pane_area); + } + + void draw(); + void set_selection(int index) { + index_ = index; + set_dirty(); + } + + bool left_side() const { return true; } + +private: + const config* game_config_; + gamemap* map_; + const game_data* data_; + const std::vector* info_; + const std::vector* summaries_; + int index_; + std::map map_cache_; +}; + +void save_preview_pane::draw() +{ + if(!dirty()) { + return; + } + + bg_restore(); + + if(index_ < 0 || index_ >= int(summaries_->size()) || info_->size() != summaries_->size()) { + return; + } + + const config& summary = *(*summaries_)[index_]; + + SDL_Surface* const screen = disp().video().getSurface(); + + const SDL_Rect area = { location().x+save_preview_border, location().y+save_preview_border, + location().w-save_preview_border*2, location().h-save_preview_border*2 }; + SDL_Rect clip_area = area; + const clip_rect_setter clipper(screen,clip_area); + + int ypos = area.y; + + const game_data::unit_type_map::const_iterator leader = data_->unit_types.find(summary["leader"]); + if(leader != data_->unit_types.end()) { + const scoped_sdl_surface image(image::get_image(leader->second.image(),image::UNSCALED)); + if(image != NULL) { + SDL_Rect image_rect = {area.x,area.y,image->w,image->h}; + ypos += image_rect.y + image_rect.h + save_preview_border; + + SDL_BlitSurface(image,NULL,screen,&image_rect); + } + } + + + std::string map_data = summary["map_data"]; + if(map_data.empty()) { + const config* const scenario = game_config_->find_child(summary["campaign_type"],"id",summary["scenario"]); + if(scenario != NULL) { + map_data = (*scenario)["map_data"]; + if(map_data.empty() && (*scenario)["map"].empty() == false) { + try { + map_data = read_map((*scenario)["map"]); + } catch(io_exception& e) { + std::cerr << "could not read map '" << (*scenario)["map"] << "': " << e.what() << "\n"; + } + } + } + } + + SDL_Surface* map_surf = NULL; + + if(map_data.empty() == false) { + const std::map::const_iterator itor = map_cache_.find(map_data); + if(itor != map_cache_.end()) { + map_surf = itor->second; + } else if(map_ != NULL) { + try { + map_->read(map_data); + + map_surf = image::getMinimap(100,100,*map_,0,NULL); + if(map_surf != NULL) { + map_cache_.insert(std::pair(map_data,shared_sdl_surface(map_surf))); + } + } catch(gamemap::incorrect_format_exception&) { + } + } + } + + if(map_surf != NULL) { + SDL_Rect map_rect = {area.x + area.w - map_surf->w,area.y,map_surf->w,map_surf->h}; + ypos = maximum(ypos,map_rect.y + map_rect.h + save_preview_border); + SDL_BlitSurface(map_surf,NULL,screen,&map_rect); + } + + char time_buf[256]; + const size_t res = strftime(time_buf,sizeof(time_buf),string_table["date_format"].c_str(),localtime(&((*info_)[index_].time_modified))); + if(res == 0) { + time_buf[0] = 0; + } + + std::stringstream str; + + // escape all special characters in filenames + std::string name = (*info_)[index_].name; + str << font::BOLD_TEXT << config::escape(name) << "\n" << time_buf; + + if(summary["corrupt"] == "yes") { + str << "\n" << string_table["save_invalid"]; + } else if(summary["campaign_type"] != "") { + str << "\n"; + + const std::string& campaign_type = summary["campaign_type"]; + if(campaign_type == "scenario") { + str << translate_string("campaign_button"); + } else if(campaign_type == "multiplayer") { + str << translate_string("multiplayer_button"); + } else if(campaign_type == "tutorial") { + str << translate_string("tutorial_button"); + } else { + str << translate_string(campaign_type); + } + + str << "\n"; + + if(summary["snapshot"] == "no" && summary["replay"] == "yes") { + str << translate_string("replay"); + } else if(summary["turn"] != "") { + str << translate_string("turn") << " " << summary["turn"]; + } else { + str << string_table["scenario_start"]; + } + + str << "\n" << translate_string("difficulty") << ": " << translate_string(summary["difficulty"]); + } + + font::draw_text(&disp(),area,12,font::NORMAL_COLOUR,str.str(),area.x,ypos,NULL,true); +} + } //end anon namespace std::string load_game_dialog(display& disp, const config& game_config, const game_data& data, bool* show_replay) { std::vector games = get_saves_list(); - delete_save save_deleter(disp,games); - gui::dialog_button delete_button(&save_deleter,string_table["delete_save"]); - std::vector buttons; - buttons.push_back(delete_button); - if(games.empty()) { gui::show_dialog(disp,NULL, string_table["no_saves_heading"], @@ -230,6 +388,11 @@ std::string load_game_dialog(display& disp, const config& game_config, const gam summaries.push_back(&cfg); } + delete_save save_deleter(disp,games,summaries); + gui::dialog_button delete_button(&save_deleter,string_table["delete_save"]); + std::vector buttons; + buttons.push_back(delete_button); + bool generate_summaries = !no_summary.empty(); //if there are more than 5 saves without a summary, it may take a substantial @@ -300,106 +463,20 @@ std::string load_game_dialog(display& disp, const config& game_config, const gam write_save_index(); } - util::scoped_ptr map_ptr(NULL); - string_map map_cache; - std::vector items; for(i = games.begin(); i != games.end(); ++i) { std::string name = i->name; name.resize(minimum(name.size(),40)); - char time_buf[256]; - const size_t res = strftime(time_buf,sizeof(time_buf),string_table["date_format"].c_str(),localtime(&(i->time_modified))); - if(res == 0) - time_buf[0] = 0; - - std::stringstream str; - - config& summary = *summaries[i - games.begin()]; - const game_data::unit_type_map::const_iterator leader = data.unit_types.find(summary["leader"]); - if(leader != data.unit_types.end()) { - str << "&" << leader->second.image() << ","; - } else { - str << ","; - } - - // escape all special characters in filenames - str << font::BOLD_TEXT << config::escape(name) << "\n" << time_buf; - - if(summary["corrupt"] == "yes") { - str << "\n" << string_table["save_invalid"]; - } else if(summary["campaign_type"] != "") { - str << "\n"; - - const std::string& campaign_type = summary["campaign_type"]; - if(campaign_type == "scenario") { - str << translate_string("campaign_button"); - } else if(campaign_type == "multiplayer") { - str << translate_string("multiplayer_button"); - } else if(campaign_type == "tutorial") { - str << translate_string("tutorial_button"); - } else { - str << translate_string(campaign_type); - } - - str << "\n"; - - if(summary["snapshot"] == "no" && summary["replay"] == "yes") { - str << translate_string("replay"); - } else if(summary["turn"] != "") { - str << translate_string("turn") << " " << summary["turn"]; - } else { - str << string_table["scenario_start"]; - } - - str << "\n" << translate_string("difficulty") << ": " << translate_string(summary["difficulty"]); - - std::string map_data = summary["map_data"]; - if(map_data.empty()) { - const config* const scenario = game_config.find_child(summary["campaign_type"],"id",summary["scenario"]); - if(scenario != NULL) { - map_data = (*scenario)["map_data"]; - if(map_data.empty() && (*scenario)["map"].empty() == false) { - try { - map_data = read_map((*scenario)["map"]); - } catch(io_exception& e) { - std::cerr << "could not read map '" << (*scenario)["map"] << "': " << e.what() << "\n"; - } - } - } - } - - if(map_data.empty() == false) { - const string_map::const_iterator itor = map_cache.find(map_data); - if(itor != map_cache.end()) { - str << ",&" << itor->second; - } else { - - try { - if(map_ptr == NULL) { - map_ptr.assign(new gamemap(game_config,map_data)); - } else { - map_ptr->read(map_data); - } - - SDL_Surface* const minimap = image::getMinimap(72,72,*map_ptr,0,NULL); - if(minimap != NULL) { - const std::string id = "_map_image_" + name; - image::register_image(id,minimap); - str << ",&" << id; - - map_cache[map_data] = id; - } - } catch(gamemap::incorrect_format_exception& e) { - } - } - } - - } - - items.push_back(str.str()); + items.push_back(name); } + gamemap map_obj(game_config,""); + + std::vector preview_panes; + save_preview_pane save_preview(disp,game_config,&map_obj,data,games,summaries); + preview_panes.push_back(&save_preview); + //create an option for whether the replay should be shown or not std::vector options; @@ -409,7 +486,7 @@ std::string load_game_dialog(display& disp, const config& game_config, const gam const int res = gui::show_dialog(disp,NULL, string_table["load_game_heading"], string_table["load_game_message"], - gui::OK_CANCEL,&items,NULL,"",NULL,NULL,&options,-1,-1,NULL,&buttons); + gui::OK_CANCEL,&items,&preview_panes,"",NULL,NULL,&options,-1,-1,NULL,&buttons); if(res == -1) return ""; @@ -505,4 +582,254 @@ int show_file_chooser_dialog(display &disp, std::string &filename, } } -} //end namespace dialogs +namespace { + static const SDL_Rect unit_preview_size = {-150,-350,150,350}; + static const int unit_preview_border = 10; +} + +unit_preview_pane::unit_preview_pane(display& disp, const gamemap* map, const unit& u, bool on_left_side) + : gui::preview_pane(disp), details_button_(disp,string_table["action_describeunit"]), + map_(map), units_(&unit_store_), index_(0), left_(on_left_side) +{ + set_location(unit_preview_size); + unit_store_.push_back(u); +} + +unit_preview_pane::unit_preview_pane(display& disp, const gamemap* map, const std::vector& units, bool on_left_side) + : gui::preview_pane(disp), details_button_(disp,string_table["action_describeunit"]), + map_(map), units_(&units), index_(0), left_(on_left_side) +{ + set_location(unit_preview_size); +} + +bool unit_preview_pane::left_side() const +{ + return left_; +} + +void unit_preview_pane::set_selection(int index) +{ + index = minimum(int(units_->size()-1),index); + if(index != index_ && index >= 0) { + index_ = index; + set_dirty(); + if(map_ != NULL) { + details_button_.set_dirty(); + } + } +} + +void unit_preview_pane::draw() +{ + if(!dirty()) { + return; + } + + bg_restore(); + + if(index_ < 0 || index_ >= int(units_->size())) { + return; + } + + const unit& u = (*units_)[index_]; + + set_dirty(false); + + SDL_Surface* const screen = disp().video().getSurface(); + + const SDL_Rect area = { location().x+unit_preview_border, location().y+unit_preview_border, + location().w-unit_preview_border*2, location().h-unit_preview_border*2 }; + SDL_Rect clip_area = area; + const clip_rect_setter clipper(screen,clip_area); + + scoped_sdl_surface unit_image(image::get_image(u.type().image(),image::UNSCALED)); + if(left_side() == false && unit_image != NULL) { + unit_image.assign(image::reverse_image(unit_image)); + } + + if(unit_image != NULL) { + SDL_Rect image_rect = {area.x,area.y,unit_image->w,unit_image->h}; + SDL_BlitSurface(unit_image,NULL,screen,&image_rect); + } + + std::stringstream details; + details << font::BOLD_TEXT << u.description() << "\n" + << font::BOLD_TEXT << u.type().language_name() + << "\n" << font::SMALL_TEXT << "(" << string_table["level"] << " " + << u.type().level() << ")\n" + << translate_string(unit_type::alignment_description(u.type().alignment())) + << "\n" + << u.traits_description() << "\n"; + + const std::vector& abilities = u.type().abilities(); + for(std::vector::const_iterator a = abilities.begin(); a != abilities.end(); ++a) { + details << translate_string_default("ability_" + *a, *a) << "\n"; + } + + //display in green/white/red depending on hitpoints + if(u.hitpoints() <= u.max_hitpoints()/3) + details << font::BAD_TEXT; + else if(u.hitpoints() > 2*(u.max_hitpoints()/3)) + details << font::GOOD_TEXT; + + details << string_table["hp"] << ": " << u.hitpoints() + << "/" << u.max_hitpoints() << "\n"; + + if(u.type().advances_to().empty()) { + details << string_table["xp"] << ": " << u.experience() << "/-"; + } else { + //if killing a unit the same level as us would level us up, + //then display in green + if(u.max_experience() - u.experience() < game_config::kill_experience) { + details << font::GOOD_TEXT; + } + + details << string_table["xp"] << ": " << u.experience() << "/" << u.max_experience(); + } + + details << "\n" + << string_table["moves"] << ": " << u.movement_left() << "/" + << u.total_movement() + << "\n"; + + const std::vector& attacks = u.attacks(); + for(std::vector::const_iterator at_it = attacks.begin(); + at_it != attacks.end(); ++at_it) { + + const std::string& lang_weapon = string_table["weapon_name_" + at_it->name()]; + const std::string& lang_type = string_table["weapon_type_" + at_it->type()]; + const std::string& lang_special = string_table["weapon_special_" + at_it->special()]; + details << "\n" + << (lang_weapon.empty() ? at_it->name():lang_weapon) << " (" + << (lang_type.empty() ? at_it->type():lang_type) << ")\n" + << (lang_special.empty() ? at_it->special():lang_special)<<"\n" + << at_it->damage() << "-" << at_it->num_attacks() << " -- " + << (at_it->range() == attack_type::SHORT_RANGE ? + string_table["short_range"] : + string_table["long_range"]); + + if(at_it->hexes() > 1) { + details << " (" << at_it->hexes() << ")"; + } + + details << "\n\n"; + } + + const SDL_Rect& text_area = font::draw_text(&disp(),area,12,font::NORMAL_COLOUR,details.str(), + area.x,area.y + (unit_image != NULL ? unit_image->h : 0) + unit_preview_border); + if(map_ != NULL) { + const SDL_Rect button_loc = {area.x + unit_preview_border,area.y + area.h - details_button_.location().h - unit_preview_border, + details_button_.location().w,details_button_.location().h}; + details_button_.set_location(button_loc); + } +} + +void unit_preview_pane::process() +{ + if(map_ != NULL && details_button_.pressed() && index_ >= 0 && index_ < int(units_->size())) { + + show_unit_description(disp(),*map_,(*units_)[index_]); + } +} + +void show_unit_description(display& disp, const gamemap& map, const unit& u) +{ + const std::string description = u.unit_description() + + "\n\n" + string_table["see_also"]; + + std::vector options; + + options.push_back(string_table["terrain_info"]); + options.push_back(string_table["attack_resistance"]); + options.push_back(string_table["close_window"]); + + const scoped_sdl_surface profile(image::get_image(u.type().image_profile(),image::SCALED)); + + const int res = gui::show_dialog(disp, profile, u.type().language_name(), + description,gui::MESSAGE, &options); + if(res == 0) { + show_unit_terrain_table(disp,map,u); + } else if(res == 1) { + show_unit_resistance(disp,u); + } +} + +void show_unit_resistance(display& disp, const unit& u) +{ + std::vector items; + items.push_back(string_table["attack_type"] + "," + string_table["attack_resistance"]); + const std::map& table = u.type().movement_type().damage_table(); + for(std::map::const_iterator i = table.begin(); i != table.end(); ++i) { + int resistance = 100 - atoi(i->second.c_str()); + + //if resistance is less than 0, display in red + const char prefix = resistance < 0 ? font::BAD_TEXT : font::NULL_MARKUP; + + const std::string& lang_weapon = string_table["weapon_type_" + i->first]; + const std::string& weap = lang_weapon.empty() ? i->first : lang_weapon; + + std::stringstream str; + str << weap << "," << prefix << resistance << "%"; + items.push_back(str.str()); + } + + const events::event_context dialog_events_context; + dialogs::unit_preview_pane unit_preview(disp,NULL,u); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + + gui::show_dialog(disp,NULL, + u.type().language_name(), + string_table["unit_resistance_table"], + gui::MESSAGE,&items,&preview_panes); +} + +void show_unit_terrain_table(display& disp, const gamemap& map, const unit& u) +{ + std::vector items; + items.push_back(string_table["terrain"] + "," + + string_table["movement"] + "," + + string_table["defense"]); + + const unit_type& type = u.type(); + const unit_movement_type& move_type = type.movement_type(); + const std::vector& terrains = map.get_terrain_precedence(); + for(std::vector::const_iterator t = + terrains.begin(); t != terrains.end(); ++t) { + + //exclude fog and shroud + if(*t == gamemap::FOGGED || *t == gamemap::VOID_TERRAIN) { + continue; + } + + const terrain_type& info = map.get_terrain_info(*t); + if(!info.is_alias()) { + const std::string& name = map.terrain_name(*t); + const std::string& lang_name = string_table[name]; + const int moves = move_type.movement_cost(map,*t); + + std::stringstream str; + str << lang_name << ","; + if(moves < 100) + str << moves; + else + str << "--"; + + const int defense = 100 - move_type.defense_modifier(map,*t); + str << "," << defense << "%"; + + items.push_back(str.str()); + } + } + + const events::event_context dialog_events_context; + dialogs::unit_preview_pane unit_preview(disp,NULL,u); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + + gui::show_dialog(disp,NULL,u.type().language_name(), + string_table["terrain_info"], + gui::MESSAGE,&items,&preview_panes); +} + +} //end namespace dialogs \ No newline at end of file diff --git a/src/dialogs.hpp b/src/dialogs.hpp index e278360f442..656e515bc6b 100644 --- a/src/dialogs.hpp +++ b/src/dialogs.hpp @@ -16,9 +16,12 @@ #include "actions.hpp" #include "config.hpp" #include "display.hpp" +#include "map.hpp" #include "show_dialog.hpp" #include "unit.hpp" +#include "widgets/button.hpp" + namespace dialogs { //function to handle an advancing unit. If there is only one choice to advance @@ -28,7 +31,7 @@ namespace dialogs // //note that 'loc' is not a reference, because deleting an item from the units map //(when replacing the unit that is being advanced) will possibly invalidate the reference -void advance_unit(const game_data& info,unit_map& units, gamemap::location loc, +void advance_unit(const game_data& info, const gamemap& map,unit_map& units, gamemap::location loc, display& gui, bool random_choice=false); bool animate_unit_advancement(const game_data& info,unit_map& units, gamemap::location loc, display& gui, size_t choice); @@ -57,6 +60,32 @@ void unit_speak(const config& message_info, display& disp, const unit_map& units int show_file_chooser_dialog(display &displ, std::string &filename, const std::string title="Choose a File", int xloc=-1, int yloc=-1); + +class unit_preview_pane : public gui::preview_pane +{ +public: + unit_preview_pane(display& disp, const gamemap* map, const unit& u, bool left_side=true); + unit_preview_pane(display& disp, const gamemap* map, const std::vector& units, bool left_side=true); + + bool left_side() const; + void set_selection(int index); + +private: + void draw(); + void process(); + + gui::button details_button_; + const gamemap* map_; + const std::vector* units_; + std::vector unit_store_; + int index_; + bool left_; +}; + +void show_unit_description(display& disp, const gamemap& map, const unit& u); +void show_unit_resistance(display& disp, const unit& u); +void show_unit_terrain_table(display& disp, const gamemap& map, const unit& u); + } #endif diff --git a/src/display.cpp b/src/display.cpp index 575c3e36b35..0ecef25ca7f 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -2089,14 +2089,23 @@ void display::invalidate_game_status() invalidateGameStatus_ = true; } -void display::add_overlay(const gamemap::location& loc, const std::string& img) +void display::add_overlay(const gamemap::location& loc, const std::string& img, const std::string& halo) { overlays_.insert(std::pair(loc,img)); + halo_overlays_.insert(std::pair(loc,halo::add(get_location_x(loc)+hex_size()/2,get_location_y(loc)+hex_size()/2,halo))); } void display::remove_overlay(const gamemap::location& loc) { overlays_.erase(loc); + typedef std::multimap::const_iterator Itor; + std::pair itors = halo_overlays_.equal_range(loc); + while(itors.first != itors.second) { + halo::remove(itors.first->second); + ++itors.first; + } + + halo_overlays_.erase(loc); } void display::write_overlays(config& cfg) const diff --git a/src/display.hpp b/src/display.hpp index 3f609c50701..084746f3b74 100644 --- a/src/display.hpp +++ b/src/display.hpp @@ -224,7 +224,7 @@ public: //functions to add and remove overlays from locations. An overlay is an //image that is displayed on top of the tile. One tile may have multiple //overlays. remove_overlay will remove all overlays on a tile. - void add_overlay(const gamemap::location& loc, const std::string& image); + void add_overlay(const gamemap::location& loc, const std::string& image, const std::string& halo=""); void remove_overlay(const gamemap::location& loc); //function to serialize overlay data @@ -422,6 +422,7 @@ private: bool invalidateGameStatus_; std::multimap overlays_; + std::multimap halo_overlays_; bool panelsDrawn_; diff --git a/src/events.cpp b/src/events.cpp index 6f5066ca798..020287bbeb0 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -128,16 +128,20 @@ std::deque event_contexts; } //end anon namespace -event_context::event_context() +event_context::event_context(bool create) : create_(create) { - event_contexts.push_back(context()); + if(create_) { + event_contexts.push_back(context()); + } } event_context::~event_context() { - assert(event_contexts.empty() == false); + if(create_) { + assert(event_contexts.empty() == false); - event_contexts.pop_back(); + event_contexts.pop_back(); + } } handler::handler() : unicode_(SDL_EnableUNICODE(1)) diff --git a/src/events.hpp b/src/events.hpp index 584bdc4799a..f7fd83057b5 100644 --- a/src/events.hpp +++ b/src/events.hpp @@ -61,8 +61,11 @@ bool has_focus(const handler* ptr); //before their context is destroyed struct event_context { - event_context(); + event_context(bool create=true); ~event_context(); + +private: + bool create_; }; //causes events to be dispatched to all handler objects. diff --git a/src/game.cpp b/src/game.cpp index eb2e9132810..8e79e8d1da2 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -883,9 +883,12 @@ int play_game(int argc, char** argv) defines_map["MEDIUM"] = preproc_define(); } } + + state.campaign_define = campaign["define"]; } else if(res == gui::MULTIPLAYER) { state.campaign_type = "multiplayer"; state.scenario = ""; + state.campaign_define = "MULTIPLAYER"; std::vector host_or_join; host_or_join.push_back(string_table["join_game"]); @@ -968,6 +971,10 @@ int play_game(int argc, char** argv) continue; } + if(state.campaign_define.empty() == false) { + defines_map[state.campaign_define] = preproc_define(); + } + //make a new game config item based on the difficulty level std::vector line_src; config game_config; diff --git a/src/game_events.cpp b/src/game_events.cpp index 3f558b92f1f..55298b1cde5 100644 --- a/src/game_events.cpp +++ b/src/game_events.cpp @@ -657,8 +657,9 @@ bool event_handler::handle_event_command(const queued_event& event_info, const s else if(cmd == "item") { gamemap::location loc(cfg); const std::string& img = cfg["image"]; - if(!img.empty()) { - screen->add_overlay(loc,img); + const std::string& halo = cfg["halo"]; + if(!img.empty() || !halo.empty()) { + screen->add_overlay(loc,img,cfg["halo"]); screen->draw_tile(loc.x,loc.y); } } diff --git a/src/gamestatus.cpp b/src/gamestatus.cpp index a822cb63927..1bfdc0bb701 100644 --- a/src/gamestatus.cpp +++ b/src/gamestatus.cpp @@ -205,6 +205,8 @@ game_state read_game(const game_data& data, const config* cfg) if(res.difficulty.empty()) res.difficulty = "NORMAL"; + res.campaign_define = (*cfg)["campaign_define"]; + res.campaign_type = (*cfg)["campaign_type"]; if(res.campaign_type.empty()) res.campaign_type = "scenario"; @@ -272,6 +274,8 @@ void write_game(const game_state& game, config& cfg, WRITE_GAME_MODE mode) cfg["difficulty"] = game.difficulty; + cfg["campaign_define"] = game.campaign_define; + cfg.add_child("variables",game.variables); for(std::vector::const_iterator i = game.available_units.begin(); diff --git a/src/gamestatus.hpp b/src/gamestatus.hpp index b642a8eb01f..fa63545fcf7 100644 --- a/src/gamestatus.hpp +++ b/src/gamestatus.hpp @@ -131,6 +131,8 @@ struct game_state std::set can_recruit; //units the player has the ability to recruit + std::string campaign_define; //if there is a define the campaign uses to customize data + //if the game is saved mid-level, we have a series of replay steps to //take the game up to the position it was saved at. config replay_data; diff --git a/src/map.cpp b/src/map.cpp index b4ed7d513ef..aa901e848b2 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -252,9 +252,6 @@ void gamemap::read(const std::string& data) } } - if(tiles_.empty()) - throw incorrect_format_exception("empty map"); - for(size_t n = 0; n != tiles_.size(); ++n) { if(tiles_[n].size() != size_t(this->y())) { std::cerr << "Map is not rectangular!\n"; diff --git a/src/playturn.cpp b/src/playturn.cpp index 4ce3c9f0598..959b1c05feb 100644 --- a/src/playturn.cpp +++ b/src/playturn.cpp @@ -576,8 +576,6 @@ void turn_info::left_click(const SDL_MouseButtonEvent& event) const std::vector& attacks = u->second.attacks(); std::vector items; - std::vector units_list; - const int range = distance_between(u->first,enemy->first); std::vector attacks_in_range; @@ -642,7 +640,6 @@ void turn_info::left_click(const SDL_MouseButtonEvent& event) << "%,&" << stats.back().defend_icon; items.push_back(att.str()); - units_list.push_back(enemy->second); } if (best_weapon_index >= 0) { @@ -659,10 +656,21 @@ void turn_info::left_click(const SDL_MouseButtonEvent& event) std::vector buttons; buttons.push_back(gui::dialog_button(&calc_displayer,string_table["damage_calculations"])); - int res = gui::show_dialog(gui_,NULL,"", - string_table["choose_weapon"]+":\n", - gui::OK_CANCEL,&items,&units_list,"",NULL,NULL,NULL,-1,-1, - NULL,&buttons); + int res = 0; + + { + const events::event_context dialog_events_context; + dialogs::unit_preview_pane attacker_preview(gui_,&map_,u->second,true); + dialogs::unit_preview_pane defender_preview(gui_,&map_,enemy->second,false); + std::vector preview_panes; + preview_panes.push_back(&attacker_preview); + preview_panes.push_back(&defender_preview); + + res = gui::show_dialog(gui_,NULL,string_table["attack_enemy"], + string_table["choose_weapon"]+":\n", + gui::OK_CANCEL,&items,&preview_panes,"",NULL,NULL,NULL,-1,-1, + NULL,&buttons); + } cursor::set(cursor::NORMAL); @@ -701,13 +709,13 @@ void turn_info::left_click(const SDL_MouseButtonEvent& event) } catch(end_level_exception&) { //if the level ends due to a unit being killed, still see if //either the attacker or defender should advance - dialogs::advance_unit(gameinfo_,units_,selected_hex_,gui_); - dialogs::advance_unit(gameinfo_,units_,hex,gui_,!defender_human); + dialogs::advance_unit(gameinfo_,map_,units_,selected_hex_,gui_); + dialogs::advance_unit(gameinfo_,map_,units_,hex,gui_,!defender_human); throw; } - dialogs::advance_unit(gameinfo_,units_,selected_hex_,gui_); - dialogs::advance_unit(gameinfo_,units_,hex,gui_,!defender_human); + dialogs::advance_unit(gameinfo_,map_,units_,selected_hex_,gui_); + dialogs::advance_unit(gameinfo_,map_,units_,hex,gui_,!defender_human); selected_hex_ = gamemap::location(); current_route_.steps.clear(); @@ -1339,117 +1347,25 @@ void turn_info::terrain_table() { unit_map::const_iterator un = current_unit(); - if(un == units_.end()) { - return; + if(un != units_.end()) { + dialogs::show_unit_terrain_table(gui_,map_,un->second); } - - gui_.draw(); - - std::vector items; - items.push_back(string_table["terrain"] + "," + - string_table["movement"] + "," + - string_table["defense"]); - - const unit_type& type = un->second.type(); - const unit_movement_type& move_type = type.movement_type(); - const std::vector& terrains = - map_.get_terrain_precedence(); - for(std::vector::const_iterator t = - terrains.begin(); t != terrains.end(); ++t) { - - //exclude fog and shroud - if(*t == gamemap::FOGGED || *t == gamemap::VOID_TERRAIN) { - continue; - } - - const terrain_type& info = map_.get_terrain_info(*t); - if(!info.is_alias()) { - const std::string& name = map_.terrain_name(*t); - const std::string& lang_name = string_table[name]; - const int moves = move_type.movement_cost(map_,*t); - - std::stringstream str; - str << lang_name << ","; - if(moves < 100) - str << moves; - else - str << "--"; - - const int defense = 100 - move_type.defense_modifier(map_,*t); - str << "," << defense << "%"; - - items.push_back(str.str()); - } - } - - const std::vector units_list(items.size(),un->second); - const scoped_sdl_surface unit_image(image::get_image(un->second.type().image_profile(),image::UNSCALED)); - gui::show_dialog(gui_,unit_image,un->second.type().language_name(), - string_table["terrain_info"], - gui::MESSAGE,&items,&units_list); } void turn_info::attack_resistance() { const unit_map::const_iterator un = current_unit(); - if(un == units_.end()) - return; - - gui_.draw(); - - std::vector items; - items.push_back(string_table["attack_type"] + "," + - string_table["attack_resistance"]); - const std::map& table = - un->second.type().movement_type().damage_table(); - for(std::map::const_iterator i - = table.begin(); i != table.end(); ++i) { - int resistance = 100 - atoi(i->second.c_str()); - - //if resistance is less than 0, display in red - const char prefix = resistance < 0 ? font::BAD_TEXT : font::NULL_MARKUP; - - const std::string& lang_weapon = string_table["weapon_type_" + i->first]; - const std::string& weap = lang_weapon.empty() ? i->first : lang_weapon; - - std::stringstream str; - str << weap << "," << prefix << resistance << "%"; - items.push_back(str.str()); + if(un != units_.end()) { + dialogs::show_unit_resistance(gui_,un->second); } - const std::vector units_list(items.size(), un->second); - const scoped_sdl_surface unit_image(image::get_image(un->second.type().image_profile(),image::UNSCALED)); - gui::show_dialog(gui_,unit_image, - un->second.type().language_name(), - string_table["unit_resistance_table"], - gui::MESSAGE,&items,&units_list); } void turn_info::unit_description() { const unit_map::const_iterator un = current_unit(); - if(un == units_.end()) { - return; - } - - const std::string description = un->second.unit_description() - + "\n\n" + string_table["see_also"]; - - std::vector options; - - options.push_back(string_table["terrain_info"]); - options.push_back(string_table["attack_resistance"]); - options.push_back(string_table["close_window"]); - - const scoped_sdl_surface unit_image(image::get_image(un->second.type().image_profile(), image::UNSCALED)); - - const int res = gui::show_dialog(gui_,unit_image, - un->second.type().language_name(), - description,gui::MESSAGE, &options); - if(res == 0) { - terrain_table(); - } else if(res == 1) { - attack_resistance(); + if(un != units_.end()) { + dialogs::show_unit_description(gui_,map_,un->second); } } @@ -1657,9 +1573,19 @@ void turn_info::recruit() return; } - const int recruit_res = gui::show_dialog(gui_,NULL,"", - string_table["recruit_unit"] + ":\n", - gui::OK_CANCEL,&items,&sample_units); + int recruit_res = 0; + + { + const events::event_context dialog_events_context; + dialogs::unit_preview_pane unit_preview(gui_,&map_,sample_units); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + + recruit_res = gui::show_dialog(gui_,NULL,"", + string_table["recruit_unit"] + ":\n", + gui::OK_CANCEL,&items,&preview_panes); + } + if(recruit_res != -1) { do_recruit(item_keys[recruit_res]); } @@ -1814,11 +1740,21 @@ void turn_info::recall() std::vector buttons; buttons.push_back(delete_button); - const int res = gui::show_dialog(gui_,NULL,"", - string_table["select_unit"] + ":\n", - gui::OK_CANCEL,&options, - &recall_list,"",NULL, - NULL,NULL,-1,-1,NULL,&buttons); + int res = 0; + + { + const events::event_context dialog_events_context; + dialogs::unit_preview_pane unit_preview(gui_,&map_,recall_list); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + + res = gui::show_dialog(gui_,NULL,"", + string_table["select_unit"] + ":\n", + gui::OK_CANCEL,&options, + &preview_panes,"",NULL, + NULL,NULL,-1,-1,NULL,&buttons); + } + if(res >= 0) { unit& un = recall_list[res]; gamemap::location loc = last_hex_; @@ -1899,8 +1835,18 @@ void turn_info::create_unit() unit_choices.back().new_turn(); } - const int choice = gui::show_dialog(gui_,NULL,"","Create unit (debug):", - gui::OK_CANCEL,&options,&unit_choices); + int choice = 0; + + { + const events::event_context dialog_events_context; + dialogs::unit_preview_pane unit_preview(gui_,&map_,unit_choices); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + + choice = gui::show_dialog(gui_,NULL,"","Create unit (debug):", + gui::OK_CANCEL,&options,&preview_panes); + } + if(choice >= 0 && choice < unit_choices.size()) { units_.erase(last_hex_); units_.insert(std::pair(last_hex_,unit_choices[choice])); @@ -1967,8 +1913,18 @@ void turn_info::unit_list() units_list.push_back(i->second); } - const int selected = gui::show_dialog(gui_,NULL,string_table["unit_list"],"", - gui::OK_ONLY,&items,&units_list); + int selected = 0; + + { + const events::event_context dialog_events_context; + dialogs::unit_preview_pane unit_preview(gui_,&map_,units_list); + std::vector preview_panes; + preview_panes.push_back(&unit_preview); + + selected = gui::show_dialog(gui_,NULL,string_table["unit_list"],"", + gui::OK_ONLY,&items,&preview_panes); + } + if(selected > 0 && selected < int(locations_list.size())) { const gamemap::location& loc = locations_list[selected]; gui_.scroll_to_tile(loc.x,loc.y,display::WARP); diff --git a/src/show_dialog.cpp b/src/show_dialog.cpp index 7ac62f19c33..3d0fa209fc8 100644 --- a/src/show_dialog.cpp +++ b/src/show_dialog.cpp @@ -316,7 +316,7 @@ int show_dialog(display& disp, SDL_Surface* image, const std::string& caption, const std::string& msg, DIALOG_TYPE type, const std::vector* menu_items_ptr, - const std::vector* units_ptr, + const std::vector* preview_panes, const std::string& text_widget_label, std::string* text_widget_text, dialog_action* action, std::vector* options, int xloc, int yloc, @@ -327,7 +327,10 @@ int show_dialog(display& disp, SDL_Surface* image, std::cerr << "showing dialog '" << caption << "' '" << msg << "'\n"; - const events::event_context dialog_events_context; + //create the event context, but only activate it if we don't have preview panes. + //the presence of preview panes indicates that the caller will create the context, + //so that their preview panes may fall inside it. + const events::event_context dialog_events_context(preview_panes == NULL); const dialog_manager manager; const events::resize_lock prevent_resizing; @@ -335,9 +338,6 @@ int show_dialog(display& disp, SDL_Surface* image, const std::vector& menu_items = (menu_items_ptr == NULL) ? std::vector() : *menu_items_ptr; - const std::vector empty_units; - const std::vector& units = (units_ptr == NULL) ? empty_units : *units_ptr; - static const int message_font_size = 16; static const int caption_font_size = 18; @@ -459,6 +459,19 @@ int show_dialog(display& disp, SDL_Surface* image, } } + size_t preview_pane_height = 0, left_preview_pane_width = 0, right_preview_pane_width = 0; + if(preview_panes != NULL) { + for(std::vector::const_iterator i = preview_panes->begin(); i != preview_panes->end(); ++i) { + const SDL_Rect& rect = (**i).location(); + preview_pane_height = maximum(rect.h,preview_pane_height); + if((**i).left_side()) { + left_preview_pane_width += rect.w; + } else { + right_preview_pane_width += rect.w; + } + } + } + const int left_padding = 10; const int right_padding = 10; const int image_h_padding = image != NULL ? 10 : 0; @@ -489,45 +502,62 @@ int show_dialog(display& disp, SDL_Surface* image, padding_height + menu_.height() + text_widget_height + check_button_height; - if(xloc <= -1 || yloc <= -1) { - xloc = scr->w/2 - total_width/2; - yloc = scr->h/2 - total_height/2; - } - - int unitx = 0; - int unity = 0; - //if we are showing a dialog with unit details, then we have - //to make more room for it - if(!units.empty()) { - xloc += scr->w/10; - unitx = xloc - 300; - if(unitx < 10) - unitx = 10; - - unity = yloc; - } - const int border_padding = 10; - const int frame_width = total_width + border_padding*2; - const int frame_height = total_height + border_padding*2; + int frame_width = total_width + border_padding*2; + int xframe = maximum(0,xloc >= 0 ? xloc : scr->w/2 - (frame_width + left_preview_pane_width + right_preview_pane_width)/2); + + const int frame_height = maximum(int(preview_pane_height),total_height + border_padding*2); + + if(xloc <= -1 || yloc <= -1) { + xloc = xframe + left_preview_pane_width; + yloc = scr->h/2 - frame_height/2; + } if(xloc + frame_width > scr->w) { xloc = scr->w - frame_width; + if(xloc < xframe) { + xframe = xloc; + } } if(yloc + frame_height > scr->h) { yloc = scr->h - frame_height; - } + } std::vector buttons_ptr; for(std::vector