titlescreen.cpp

Go to the documentation of this file.
00001 /* $Id: titlescreen.cpp 26801 2008-05-23 19:05:31Z mordante $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 /**
00016  *  @file titlescreen.cpp
00017  *  Shows the titlescreen, with main-menu and tip-of-the-day.
00018  * 
00019  *  The menu consists of buttons, such als Start-Tutorial, Start-Campaign,
00020  *  Load-Game, etc.  As decoration, the wesnoth-logo and a landmap in the
00021  *  background are shown.
00022  */
00023 
00024 #include "global.hpp"
00025 
00026 #include "config.hpp"
00027 #include "construct_dialog.hpp"
00028 #include "cursor.hpp"
00029 #include "game_display.hpp"
00030 #include "events.hpp"
00031 #include "filesystem.hpp"
00032 #include "game_config.hpp"
00033 #include "hotkeys.hpp"
00034 #include "key.hpp"
00035 #include "gettext.hpp"
00036 #include "log.hpp"
00037 #include "marked-up_text.hpp"
00038 #include "preferences_display.hpp"
00039 #include "sdl_utils.hpp"
00040 #include "show_dialog.hpp"
00041 #include "titlescreen.hpp"
00042 #include "util.hpp"
00043 #include "video.hpp"
00044 #include "serialization/parser.hpp"
00045 #include "serialization/preprocessor.hpp"
00046 #include <algorithm>
00047 #include <vector>
00048 
00049 #include "SDL_ttf.h"
00050 
00051 /** Log info-messages to stdout during the game, mainly for debugging */
00052 #define LOG_DP LOG_STREAM(info, display)
00053 /** Log error-messages to stdout during the game, mainly for debugging */
00054 #define ERR_DP LOG_STREAM(err, display)
00055 #define LOG_CONFIG LOG_STREAM(info, config)
00056 #define ERR_CONFIG LOG_STREAM(err, config)
00057 
00058 /**
00059  *  Fade-in the wesnoth-logo.
00060  * 
00061  *  Animation-effect: scroll-in from right. \n
00062  *  Used only once, after the game is started.
00063  * 
00064  *  @param  screen  surface to operate on
00065  *  @param  xpos    x-position of logo
00066  *  @param  ypos    y-position of logo
00067  * 
00068  *  @return     Result of running the routine
00069  *  @retval true    operation finished (successful or not)
00070  *  @retval false   operation failed (because modeChanged), need to retry
00071  */ 
00072 static bool fade_logo(game_display& screen, int xpos, int ypos)
00073 {
00074     const surface logo(image::get_image(game_config::game_logo));
00075     if(logo == NULL) {
00076         ERR_DP << "Could not find game logo\n";
00077         return true;
00078     }
00079 
00080     surface const fb = screen.video().getSurface();
00081 
00082     if(fb == NULL || xpos < 0 || ypos < 0 || xpos + logo->w > fb->w || ypos + logo->h > fb->h) {
00083         return true;
00084     }
00085 
00086     // Only once, when the game is first started, the logo fades in
00087     static bool faded_in = false;
00088 //  static bool faded_in = true;    // for faster startup: mark logo as 'has already faded in'
00089 
00090     CKey key;
00091     bool last_button = key[SDLK_ESCAPE] || key[SDLK_SPACE];
00092 
00093     LOG_DP << "fading logo in....\n";
00094     LOG_DP << "logo size: " << logo->w << "," << logo->h << "\n";
00095 
00096     for(int x = 0; x != logo->w; ++x) {
00097         SDL_Rect srcrect = {x,0,1,logo->h};
00098         SDL_Rect dstrect = {xpos+x,ypos,1,logo->h};
00099         SDL_BlitSurface(logo,&srcrect,fb,&dstrect);
00100 
00101         update_rect(dstrect);
00102 
00103         if(!faded_in && (x%5) == 0) {
00104 
00105             const bool new_button = key[SDLK_ESCAPE] || key[SDLK_SPACE] ||
00106                                     key[SDLK_RETURN] || key[SDLK_KP_ENTER] ;
00107             if(new_button && !last_button) {
00108                 faded_in = true;
00109             }
00110 
00111             last_button = new_button;
00112 
00113             screen.update_display();
00114             screen.delay(10);
00115 
00116             events::pump();
00117             if(screen.video().modeChanged()) {
00118                 faded_in = true;
00119                 return false;
00120             }
00121         }
00122 
00123     }
00124 
00125     LOG_DP << "logo faded in\n";
00126 
00127     faded_in = true;
00128     return true;
00129 }
00130 
00131 
00132 /** Read the file with the tips-of-the-day. */
00133 static void read_tips_of_day(config& tips_of_day)
00134 {
00135     tips_of_day.clear();
00136     LOG_CONFIG << "Loading tips of day\n";
00137     try {
00138         scoped_istream stream = preprocess_file("data/hardwired/tips.cfg");
00139         read(tips_of_day, *stream);
00140     } catch(config::error&) {
00141         ERR_CONFIG << "Could not read data/hardwired/tips.cfg\n";
00142     }
00143 
00144     //we shuffle the tips after each initial loading
00145     config::child_itors tips = tips_of_day.child_range("tip");
00146     if (tips.first != tips.second) {
00147         std::random_shuffle(tips.first, tips.second);
00148     }
00149 }
00150 
00151 /** Go to the next tips-of-the-day */
00152 static void next_tip_of_day(config& tips_of_day, bool reverse = false)
00153 {
00154     // we just rotate the tip list, to avoid the need to keep track
00155     // of the current one, and keep it valid, cycle it, etc... 
00156     config::child_itors tips = tips_of_day.child_range("tip");
00157     if (tips.first != tips.second) {
00158         config::child_iterator direction = reverse ? tips.first+1 : tips.second-1;
00159         std::rotate(tips.first, direction, tips.second);
00160     }
00161 }
00162 
00163 /** Return the text for one of the tips-of-the-day. */
00164 static const config* get_tip_of_day(config& tips_of_day)
00165 {
00166     if (tips_of_day.empty()) {
00167         read_tips_of_day(tips_of_day);
00168     }
00169 
00170     const config::child_list& tips = tips_of_day.get_children("tip");
00171     
00172     // next_tip_of_day rotate tips, so better stay iterator-safe
00173     for (size_t t=0; t < tips.size(); t++, next_tip_of_day(tips_of_day)) {
00174         const config* tip = tips.front();
00175         if (tip == NULL) continue;
00176 
00177         const std::vector<std::string> needed_units = utils::split((*tip)["encountered_units"],',');
00178         if (needed_units.empty()) {
00179             return tip;
00180         }
00181         const std::set<std::string>& seen_units = preferences::encountered_units();
00182 
00183         // test if one of the listed unit types is already encountered
00184         // if if's a number, test if we have encountered more than this
00185         for (std::vector<std::string>::const_iterator i = needed_units.begin();
00186                 i != needed_units.end(); i++) {
00187             int needed_units_nb = lexical_cast_default<int>(*i,-1);
00188             if (needed_units_nb !=-1) {
00189                 if (needed_units_nb <= static_cast<int>(seen_units.size())) {
00190                     return tip;
00191                 }
00192             } else if (seen_units.find(*i) != seen_units.end()) {
00193                 return tip;
00194             }
00195         }
00196     }
00197     // not tip match, someone forget to put an always-match one
00198     return NULL;
00199 }
00200 
00201 /**
00202  *  Show one tip-of-the-day in a frame on the titlescreen.
00203  *  This frame has 2 buttons: Next-Tip, and Show-Help.
00204  */
00205 static void draw_tip_of_day(game_display& screen,
00206                             config& tips_of_day,
00207                             const gui::dialog_frame::style& style,
00208                             gui::button* const previous_tip_button,
00209                             gui::button* const next_tip_button,
00210                             gui::button* const help_tip_button,
00211                             const SDL_Rect* const main_dialog_area,
00212                             surface_restorer& tip_of_day_restorer)
00213 {
00214     if(preferences::show_tip_of_day() == false) {
00215         return;
00216     }
00217 
00218     // Restore the previous tip of day area to its old state (section of the title image).
00219     tip_of_day_restorer.restore();
00220 
00221     // Draw tip of the day
00222     const config* tip = get_tip_of_day(tips_of_day);
00223     if(tip != NULL) {
00224         int tip_width = game_config::title_tip_width * screen.w() / 1024;
00225 
00226         try {
00227             const std::string& text = 
00228                 font::word_wrap_text((*tip)["text"], font::SIZE_NORMAL, tip_width);
00229             const std::string& source = 
00230                 font::word_wrap_text((*tip)["source"], font::SIZE_NORMAL, tip_width);
00231 
00232             const int pad = game_config::title_tip_padding;
00233 
00234             SDL_Rect area = font::text_area(text,font::SIZE_NORMAL);
00235             area.w = tip_width;
00236             SDL_Rect source_area = font::text_area(source, font::SIZE_NORMAL, TTF_STYLE_ITALIC);
00237             area.w = maximum<size_t>(area.w, source_area.w) + 2*pad;
00238             area.h += source_area.h + next_tip_button->location().h + 3*pad;
00239 
00240             area.x = main_dialog_area->x - (game_config::title_tip_x * screen.w() / 1024) - area.w;
00241             area.y = main_dialog_area->y + main_dialog_area->h - area.h;
00242 
00243             // Note: The buttons' locations need to be set before the dialog frame is drawn.
00244             // Otherwise, when the buttons restore their area, they
00245             // draw parts of the old dialog frame at their old locations.
00246             // This way, the buttons draw a part of the title image,
00247             // because the call to restore above restored the area
00248             // of the old tip of the day to its initial state (the title image).
00249             int button_x = area.x + area.w - next_tip_button->location().w - pad;
00250             int button_y = area.y + area.h - pad - next_tip_button->location().h;
00251             next_tip_button->set_location(button_x, button_y);
00252             next_tip_button->set_dirty(); //force redraw even if location did not change.
00253 
00254             button_x -= previous_tip_button->location().w + pad;
00255             previous_tip_button->set_location(button_x, button_y);
00256             previous_tip_button->set_dirty();
00257 
00258             button_x = area.x + pad;
00259             help_tip_button->set_location(button_x, button_y);
00260             help_tip_button->set_dirty(); 
00261 
00262             gui::dialog_frame f(screen.video(), "", style, false);
00263             tip_of_day_restorer = surface_restorer(&screen.video(), f.layout(area).exterior);
00264             f.draw_background();
00265             f.draw_border();
00266 
00267             font::draw_text(&screen.video(), area, font::SIZE_NORMAL, font::NORMAL_COLOUR,
00268                              text, area.x + pad, area.y + pad);
00269             // todo
00270             font::draw_text(&screen.video(), area, font::SIZE_NORMAL, font::NORMAL_COLOUR,
00271                              source, area.x + area.w - source_area.w - pad,
00272                              next_tip_button->location().y - source_area.h - pad,
00273                              false, TTF_STYLE_ITALIC);
00274         } catch (utils::invalid_utf8_exception&) {
00275             LOG_STREAM(err, engine) << "Invalid utf-8 found, tips of day aren't drawn.\n";
00276             return;
00277         }
00278 
00279         LOG_DP << "drew tip of day\n";
00280     }
00281 }
00282 
00283 /**
00284  *  Draw the map image background, revision number
00285  *  and fade the log the first time
00286  */
00287 static void draw_background(game_display& screen)
00288 {
00289     bool fade_failed = false;
00290     do {
00291         int logo_x = game_config::title_logo_x * screen.w() / 1024,
00292             logo_y = game_config::title_logo_y * screen.h() / 768;
00293 
00294         /*Select a random game_title*/
00295         std::vector<std::string> game_title_list =
00296             utils::split(game_config::game_title, ',', utils::STRIP_SPACES | utils::REMOVE_EMPTY);
00297 
00298         if(game_title_list.empty()) {
00299             ERR_CONFIG << "No title image defined\n";
00300         } else {
00301             surface const title_surface(scale_surface(
00302                 image::get_image(game_title_list[rand()%game_title_list.size()]),
00303                 screen.w(), screen.h()));
00304 
00305 
00306             if (title_surface.null()) {
00307                 ERR_DP << "Could not find title image\n";
00308             } else {
00309                 screen.video().blit_surface(0, 0, title_surface);
00310                 update_rect(screen_area());
00311                 LOG_DP << "displayed title image\n";
00312             }
00313         }
00314 
00315         fade_failed = !fade_logo(screen, logo_x, logo_y);
00316     } while (fade_failed);
00317     LOG_DP << "faded logo\n";
00318 
00319     // Display Wesnoth version and (if possible) revision
00320     const std::string& version_str = _("Version") +
00321         std::string(" ") + game_config::revision;
00322 
00323     const SDL_Rect version_area = font::draw_text(NULL, screen_area(),
00324                                   font::SIZE_TINY, font::NORMAL_COLOUR,
00325                                   version_str,0,0);
00326     const size_t versiony = screen.h() - version_area.h;
00327 
00328     if(versiony < size_t(screen.h())) {
00329         draw_solid_tinted_rectangle(0, versiony - 2, version_area.w + 3, version_area.h + 2,0,0,0,0.75,screen.video().getSurface());
00330         font::draw_text(&screen.video(),screen.screen_area(),
00331                 font::SIZE_TINY, font::NORMAL_COLOUR,
00332                 version_str,0,versiony);
00333     }
00334 
00335     LOG_DP << "drew version number\n";
00336 }
00337 
00338 
00339 namespace gui {
00340 
00341 TITLE_RESULT show_title(game_display& screen, config& tips_of_day, bool redraw_background)
00342 {
00343     cursor::set(cursor::NORMAL);
00344 
00345     const preferences::display_manager disp_manager(&screen);
00346     const hotkey::basic_handler key_handler(&screen);
00347 
00348     const font::floating_label_context label_manager;
00349 
00350     screen.video().modeChanged(); // resets modeChanged value
00351 
00352     if (redraw_background)
00353         draw_background(screen);
00354 
00355     //- Texts for the menu-buttons.
00356     //- Members of this array must correspond to the enumeration TITLE_RESULT
00357     static const char* button_labels[] = {
00358                            N_("TitleScreen button^Tutorial"),
00359                            N_("TitleScreen button^Campaign"),
00360                            N_("TitleScreen button^Multiplayer"),
00361                            N_("TitleScreen button^Load"),
00362                            N_("TitleScreen button^Add-ons"),
00363 #ifdef MAP_EDITOR
00364                            N_("TitleScreen button^Editor"),
00365 #endif
00366                            N_("TitleScreen button^Language"),
00367                            N_("TitleScreen button^Preferences"),
00368                            N_("TitleScreen button^Credits"),
00369                            N_("TitleScreen button^Quit"),
00370                                 // Only the above buttons go into the menu-frame
00371                                 // Next 2 buttons go into frame for the tip-of-the-day:
00372                            N_("TitleScreen button^Previous"),
00373                            N_("TitleScreen button^Next"),
00374                            N_("TitleScreen button^Help"),
00375                                 // Next entry is no button, but shown as a mail-icon instead:
00376                            N_("TitleScreen button^Help Wesnoth") };
00377     //- Texts for the tooltips of the menu-buttons
00378     static const char* help_button_labels[] = { N_("Start a tutorial to familiarize yourself with the game"),
00379                             N_("Start a new single player campaign"),
00380                             N_("Play multiplayer (hotseat, LAN, or Internet), or a single scenario against the AI"),
00381                             N_("Load a saved game"),
00382                             N_("Download usermade campaigns, eras, or map packs"),
00383 #ifdef MAP_EDITOR
00384                             N_("Start the map editor"),
00385 #endif
00386                             N_("Change the language"),
00387                             N_("Configure the game's settings"),
00388                             N_("View the credits"),
00389                             N_("Quit the game"),
00390                             N_("Show next tip of the day"),
00391                             N_("Show Battle for Wesnoth help"),
00392                             N_("Upload statistics") };
00393 
00394     static const size_t nbuttons = sizeof(button_labels)/sizeof(*button_labels);
00395     const int menu_xbase = (game_config::title_buttons_x*screen.w())/1024;
00396     const int menu_xincr = 0;
00397 
00398 #ifdef USE_TINY_GUI
00399     const int menu_ybase = (game_config::title_buttons_y*screen.h())/768 - 15;
00400     const int menu_yincr = 15;
00401 #else
00402     const int menu_ybase = (game_config::title_buttons_y*screen.h())/768;
00403     const int menu_yincr = 35;
00404 #endif
00405 
00406     const int padding = game_config::title_buttons_padding;
00407 
00408     std::vector<button> buttons;
00409     size_t b, max_width = 0;
00410     size_t n_menubuttons = 0;
00411     for(b = 0; b != nbuttons; ++b) {
00412         buttons.push_back(button(screen.video(),sgettext(button_labels[b])));
00413         buttons.back().set_help_string(sgettext(help_button_labels[b]));
00414         max_width = maximum<size_t>(max_width,buttons.back().width());
00415 
00416         n_menubuttons = b;
00417         if(b == QUIT_GAME) break;   // Menu-frame ends at the quit-button
00418     }
00419 
00420     SDL_Rect main_dialog_area = {menu_xbase-padding, menu_ybase-padding, max_width+padding*2,
00421                                  menu_yincr*(n_menubuttons)+buttons.back().height()+padding*2};
00422 
00423     gui::dialog_frame main_frame(screen.video(), "", gui::dialog_frame::titlescreen_style, false);
00424     main_frame.layout(main_dialog_area);
00425 
00426     // we only redraw transparent parts when asked,
00427     // to prevent alpha growing
00428     if (redraw_background) {
00429         main_frame.draw_background();
00430         main_frame.draw_border();
00431     }
00432 
00433     for(b = 0; b != nbuttons; ++b) {
00434         buttons[b].set_width(max_width);
00435         buttons[b].set_location(menu_xbase + b*menu_xincr, menu_ybase + b*menu_yincr);
00436         if(b == QUIT_GAME) break;
00437     }
00438 
00439     b = TIP_PREVIOUS;
00440     gui::button previous_tip_button(screen.video(),sgettext(button_labels[b]),button::TYPE_PRESS,"lite_small");
00441     previous_tip_button.set_help_string( sgettext(button_labels[b] ));
00442 
00443     b = TIP_NEXT;
00444     gui::button next_tip_button(screen.video(),sgettext(button_labels[b]),button::TYPE_PRESS,"lite_small");
00445     next_tip_button.set_help_string( sgettext(button_labels[b] ));
00446 
00447     b = SHOW_HELP;
00448     gui::button help_tip_button(screen.video(),sgettext(button_labels[b]),button::TYPE_PRESS,"lite_small");
00449     help_tip_button.set_help_string( sgettext(button_labels[b] ));
00450 
00451     /** @todo FIXME: Translatable string is here because we WILL put text in before 1.2! */
00452     gui::button beg_button(screen.video(),("Help Wesnoth"),button::TYPE_IMAGE,"menu-button",button::MINIMUM_SPACE);
00453     beg_button.set_help_string(_("Help Wesnoth by sending us information"));
00454 
00455     next_tip_of_day(tips_of_day);
00456 
00457     surface_restorer tip_of_day_restorer;
00458 
00459     draw_tip_of_day(screen, tips_of_day, gui::dialog_frame::titlescreen_style,
00460                     &previous_tip_button, &next_tip_button, &help_tip_button, &main_dialog_area, tip_of_day_restorer);
00461 
00462     const int pad = game_config::title_tip_padding;
00463     beg_button.set_location(screen.w() - pad - beg_button.location().w,
00464         screen.h() - pad - beg_button.location().h);
00465     events::raise_draw_event();
00466 
00467     LOG_DP << "drew buttons dialog\n";
00468 
00469     CKey key;
00470 
00471     bool last_escape = key[SDLK_ESCAPE] != 0;
00472 
00473     update_whole_screen();
00474 
00475     LOG_DP << "entering interactive loop...\n";
00476 
00477     for(;;) {
00478         for(size_t b = 0; b != buttons.size(); ++b) {
00479             if(buttons[b].pressed()) {
00480                 return TITLE_RESULT(b);
00481             }
00482         }
00483 
00484         if(previous_tip_button.pressed()) {
00485             next_tip_of_day(tips_of_day, true);
00486             draw_tip_of_day(screen, tips_of_day, gui::dialog_frame::titlescreen_style,
00487                         &previous_tip_button, &next_tip_button, &help_tip_button, &main_dialog_area, tip_of_day_restorer);
00488         }
00489         if(next_tip_button.pressed()) {
00490             next_tip_of_day(tips_of_day, false);
00491             draw_tip_of_day(screen, tips_of_day, gui::dialog_frame::titlescreen_style,
00492                         &previous_tip_button, &next_tip_button, &help_tip_button, &main_dialog_area, tip_of_day_restorer);
00493         }
00494 
00495         if(help_tip_button.pressed()) {
00496             return SHOW_HELP;
00497         }
00498         if(beg_button.pressed()) {
00499             return BEG_FOR_UPLOAD;
00500         }
00501 
00502         events::raise_process_event();
00503         events::raise_draw_event();
00504 
00505         screen.flip();
00506 
00507         if(!last_escape && key[SDLK_ESCAPE])
00508             return QUIT_GAME;
00509 
00510         last_escape = key[SDLK_ESCAPE] != 0;
00511 
00512         events::pump();
00513 
00514         // If the resolution has changed due to the user resizing the screen,
00515         // or from changing between windowed and fullscreen:
00516         if(screen.video().modeChanged()) {
00517             return REDRAW_BACKGROUND;
00518         }
00519 
00520         screen.delay(20);
00521     }
00522 
00523     return QUIT_GAME;
00524 }
00525 
00526 } // namespace gui
00527 
00528 //.

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs