// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2004-2015 Steve Baker // Copyright (C) 2011-2015 Joerg Henrichs, Marianne Gagnon // // 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 3 // 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; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /** * \mainpage SuperTuxKart developer documentation * * This document contains the developer documentation for SuperTuxKart, * including the list of modules, the list of classes, the API reference, * and some pages that describe in more depth some parts of the code/engine. * * \section Overview * * Here is an overview of the high-level interactions between modules : \dot digraph interaction { race -> modes race -> tracks race -> karts modes -> tracks modes -> karts tracks -> graphics karts -> graphics tracks -> items graphics -> irrlicht guiengine -> irrlicht states_screens -> guiengine states_screens -> input guiengine -> input karts->physics tracks->physics karts -> controller input->controller tracks -> animations physics -> animations } \enddot Note that this graph is only an approximation because the real one would be much too complicated :) \section Modules \li \ref addonsgroup : Handles add-ons that can be downloaded. \li \ref animations : This module manages interpolation-based animation (of position, rotation and/or scale) \li \ref audio : This module handles audio (sound effects and music). \li \ref challenges : This module handles the challenge system, which locks features (tracks, karts modes, etc.) until the user completes some task. \li \ref config : This module handles the user configuration, the supertuxkart configuration file (which contains options usually not edited by the player) and the input configuration file. \li \ref graphics : This module contains the core graphics engine, that is mostly a thin layer on top of irrlicht providing some additional features we need for STK (like particles, more scene node types, mesh manipulation tools, material management, etc...) \li \ref guiengine : Contains the generic GUI engine (contains the widgets and the backing logic for event handling, the skin, screens and dialogs). See module @ref states_screens for the actual STK GUI screens. Note that all input comes through this module too. \li \ref widgetsgroup : Contains the various types of widgets supported by the GUI engine. \li \ref input : Contains classes for input management (keyboard and gamepad) \li \ref io : Contains generic utility classes for file I/O (especially XML handling). \li \ref items : Defines the various collectibles and weapons of STK. \li \ref karts : Contains classes that deal with the properties, models and physics of karts. \li \ref controller : Contains kart controllers, which are either human players or AIs (this module thus contains the AIs) \li \ref modes : Contains the logic for the various game modes (race, follow the leader, battle, etc.) \li \ref physics : Contains various physics utilities. \li \ref race : Contains the race information that is conceptually above what you can find in group Modes. Handles highscores, grands prix, number of karts, which track was selected, etc. \li \ref states_screens : Contains the various screens and dialogs of the STK user interface, using the facilities of the guiengine module. Also contains the stack of menus and handles state management (in-game vs menu). \li \ref tracks : Contains information about tracks, namely drivelines, checklines and track objects. \li \ref tutorial : Work in progress */ #ifdef WIN32 # ifdef __CYGWIN__ # include # endif # define WIN32_LEAN_AND_MEAN # include # ifdef _MSC_VER # include # endif #else # include #endif #include #include #include #include #include #include #include #include "main_loop.hpp" #include "achievements/achievements_manager.hpp" #include "addons/addons_manager.hpp" #include "addons/news_manager.hpp" #include "audio/music_manager.hpp" #include "audio/sfx_manager.hpp" #include "challenges/unlock_manager.hpp" #include "config/hardware_stats.hpp" #include "config/player_manager.hpp" #include "config/player_profile.hpp" #include "config/stk_config.hpp" #include "config/user_config.hpp" #include "graphics/central_settings.hpp" #include "graphics/graphics_restrictions.hpp" #include "graphics/irr_driver.hpp" #include "graphics/material_manager.hpp" #include "graphics/particle_kind_manager.hpp" #include "graphics/referee.hpp" #include "guiengine/engine.hpp" #include "guiengine/event_handler.hpp" #include "guiengine/dialog_queue.hpp" #include "input/device_manager.hpp" #include "input/input_manager.hpp" #include "input/keyboard_device.hpp" #include "input/wiimote_manager.hpp" #include "io/file_manager.hpp" #include "items/attachment_manager.hpp" #include "items/item_manager.hpp" #include "items/projectile_manager.hpp" #include "karts/controller/ai_base_lap_controller.hpp" #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/demo_world.hpp" #include "modes/profile_world.hpp" #include "network/network_config.hpp" #include "network/network_string.hpp" #include "network/servers_manager.hpp" #include "network/stk_host.hpp" #include "online/profile_manager.hpp" #include "online/request_manager.hpp" #include "race/grand_prix_manager.hpp" #include "race/highscore_manager.hpp" #include "race/history.hpp" #include "race/race_manager.hpp" #include "replay/replay_play.hpp" #include "replay/replay_recorder.hpp" #include "states_screens/main_menu_screen.hpp" #include "states_screens/register_screen.hpp" #include "states_screens/state_manager.hpp" #include "states_screens/user_screen.hpp" #include "states_screens/dialogs/message_dialog.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/command_line.hpp" #include "utils/constants.hpp" #include "utils/crash_reporting.hpp" #include "utils/leak_check.hpp" #include "utils/log.hpp" #include "utils/translation.hpp" static void cleanSuperTuxKart(); static void cleanUserConfig(); void runUnitTests(); // ============================================================================ // gamepad visualisation screen // ============================================================================ void gamepadVisualisation() { core::array irrlicht_gamepads; irr_driver->getDevice()->activateJoysticks(irrlicht_gamepads); struct Gamepad { s16 m_axis[SEvent::SJoystickEvent::NUMBER_OF_AXES]; bool m_button_state[SEvent::SJoystickEvent::NUMBER_OF_BUTTONS]; }; #define GAMEPAD_COUNT 8 // const won't work class EventReceiver : public IEventReceiver { public: Gamepad m_gamepads[GAMEPAD_COUNT]; EventReceiver() { for (int n=0; n= GAMEPAD_COUNT) return true; Gamepad& g = m_gamepads[evt.Joystick]; for (int i=0; igetDevice()->setEventReceiver(events); while (true) { if (!irr_driver->getDevice()->run()) break; video::IVideoDriver* driver = irr_driver->getVideoDriver(); const core::dimension2du size = driver ->getCurrentRenderTargetSize(); driver->beginScene(true, true, video::SColor(255,0,0,0)); for (int n=0; nm_gamepads[n]; const int MARGIN = 10; const int x = (n & 1 ? size.Width/2 + MARGIN : MARGIN ); const int w = size.Width/2 - MARGIN*2; const int h = size.Height/(GAMEPAD_COUNT/2) - MARGIN*2; const int y = size.Height/(GAMEPAD_COUNT/2)*(n/2) + MARGIN; driver->draw2DRectangleOutline( core::recti(x, y, x+w, y+h) ); const int btn_y = y + 5; const int btn_x = x + 5; const int BTN_SIZE = (w - 10)/SEvent::SJoystickEvent::NUMBER_OF_BUTTONS; for (int b=0; bdraw2DRectangle (video::SColor(255,255,0,0), core::recti(pos, size)); } driver->draw2DRectangleOutline( core::recti(pos, size) ); } const int axis_y = btn_y + BTN_SIZE + 5; const int axis_x = btn_x; const int axis_w = w - 10; const int axis_h = (h - BTN_SIZE - 15) / SEvent::SJoystickEvent::NUMBER_OF_AXES; for (int a=0; adraw2DRectangle (deadzone ? video::SColor(255,255,0,0) : video::SColor(255,0,255,0), fillbar); driver->draw2DRectangleOutline( core::recti(pos, size) ); } } driver->endScene(); } } // gamepadVisualisation // ============================================================================ /** Sets the hat mesh name depending on the current christmas mode * m_xmas_mode (0: use current date, 1: always on, 2: always off). */ void handleXmasMode() { bool xmas = false; switch(UserConfigParams::m_xmas_mode) { case 0: { int day, month; StkTime::getDate(&day, &month); // Christmat hats are shown between 17. of December // and 5th of January xmas = (month == 12 && day>=17) || (month == 1 && day <=5); break; } case 1: xmas = true; break; default: xmas = false; break; } // switch m_xmas_mode if(xmas) kart_properties_manager->setHatMeshName("christmas_hat.b3d"); } // handleXmasMode // ============================================================================ /** Determines if Easter Ears should be used * m_easter_ear_mode (0: use current date, 1: always on, 2: always off). */ bool isEasterMode(int day, int month, int year, int before_after_days) { switch (UserConfigParams::m_easter_ear_mode) { case 0: { // Compute Easter date, based on wikipedia formula // http://en.wikipedia.org/wiki/Computus int a = year % 19; int b = year >> 2; int c = int(floor(b / 25)) + 1; int d = (c * 3) >> 2; int e = ((a * 19) - int(floor((c * 8 + 5) / 25)) + d + 15) % 30; e += (29578 - a - e * 32) >> 10; e -= ((year % 7) + b - d + e + 2) % 7; d = e >> 5; int easter_day = e - d * 31; int easter_month = d + 3; int easter_start_day = easter_day - before_after_days; int easter_start_month = easter_month; if (easter_start_day < 1) { easter_start_day += 31; // Month is April, going into March easter_start_month--; } int easter_end_day = easter_day + before_after_days; int easter_end_month = easter_month; int month_length = easter_end_month == 3 ? 31 : 30; if (easter_end_day > month_length) { easter_end_day -= month_length; easter_end_month++; } return (month > easter_start_month || (month == easter_start_month && day >= easter_start_day)) && (month < easter_end_month || (month == easter_end_month && day <= easter_end_day)); break; } case 1: return true; break; default: return false; break; } // switch m_xmas_mode } // isEasterMode(day, month, year, before_after_days) // ============================================================================ /** Wrapper around handleEasterEarMode(day, month, year, before_after_days). */ void handleEasterEarMode() { int day, month, year; StkTime::getDate(&day, &month, &year); if (isEasterMode(day, month, year, /*before_after_days*/5)) kart_properties_manager->setHatMeshName("easter_ears.b3d"); } // handleEasterMode // ============================================================================ /** This function sets up all data structure for an immediate race start. * It is used when the -N or -R command line options are used. */ void setupRaceStart() { // Skip the start screen. This esp. means that no login screen is // displayed (if necessary), so we have to make sure there is // a current player PlayerManager::get()->enforceCurrentPlayer(); InputDevice *device; // Use keyboard 0 by default in --no-start-screen device = input_manager->getDeviceManager()->getKeyboard(0); // Create player and associate player with keyboard StateManager::get()->createActivePlayer( PlayerManager::get()->getPlayer(0), device); if (!kart_properties_manager->getKart(UserConfigParams::m_default_kart)) { Log::warn("main", "Kart '%s' is unknown so will use the " "default kart.", UserConfigParams::m_default_kart.c_str()); race_manager->setPlayerKart(0, UserConfigParams::m_default_kart.getDefaultValue()); } else { // Set up race manager appropriately race_manager->setPlayerKart(0, UserConfigParams::m_default_kart); } // ASSIGN should make sure that only input from assigned devices // is read. input_manager->getDeviceManager()->setAssignMode(ASSIGN); } // setupRaceStart // ---------------------------------------------------------------------------- /** Prints help for command line options to stdout. */ void cmdLineHelp() { fprintf(stdout, "Usage: %s [OPTIONS]\n\n" "Run SuperTuxKart, a racing game with go-kart that features" " the Tux and friends.\n\n" "Options:\n" " -N, --no-start-screen Immediately start race without showing a " "menu.\n" " -R, --race-now Same as -N but also skip the ready-set-go phase" " and the music.\n" " -t, --track=NAME Start at track NAME.\n" " --gp=NAME Start the specified Grand Prix.\n" " --add-gp-dir=DIR Load Grand Prix in DIR. Setting will be saved " "inconfig.xml under additional_gp_directory. Use " "--add-gp-dir=\"\" to unset.\n" " --stk-config=FILE use ./data/FILE instead of " "./data/stk_config.xml\n" " -k, --numkarts=NUM Number of karts on the racetrack.\n" " --kart=NAME Use kart number NAME.\n" " --ai=a,b,... Use the karts a, b, ... for the AI.\n" " --laps=N Define number of laps to N.\n" " --mode=N N=1 novice, N=2 driver, N=3 racer.\n" " --type=N N=0 Normal, N=1 Time trial, N=2 FTL\n" " --reverse Play track in reverse (if allowed)\n" " -f, --fullscreen Select fullscreen display.\n" " -w, --windowed Windowed display (default).\n" " -s, --screensize=WxH Set the screen size (e.g. 320x200).\n" " -v, --version Show version of SuperTuxKart.\n" " --trackdir=DIR A directory from which additional tracks are " "loaded.\n" " --profile-laps=n Enable automatic driven profile mode for n " "laps.\n" " --profile-time=n Enable automatic driven profile mode for n " "seconds.\n" " --no-graphics Do not display the actual race.\n" " --with-profile Enables the profile mode.\n" " --demo-mode=t Enables demo mode after t seconds idle time in " "main menu.\n" " --demo-tracks=t1,t2 List of tracks to be used in demo mode. No\n" " spaces are allowed in the track names.\n" " --demo-laps=n Number of laps in a demo.\n" " --demo-karts=n Number of karts to use in a demo.\n" // " --history Replay history file 'history.dat'.\n" // " --history=n Replay history file 'history.dat' using:\n" // " n=1: recorded positions\n" // " n=2: recorded key strokes\n" " --server=name Start a server (not a playing client).\n" " --lan-server=name Start a LAN server (not a playing client).\n" " --server-password= Sets a password for a server (both client&server).\n" " --login=s Automatically log in (set the login).\n" " --password=s Automatically log in (set the password).\n" " --port=n Port number to use.\n" " --max-players=n Maximum number of clients (server only).\n" " --no-console Does not write messages in the console but to\n" " stdout.log.\n" " --console Write messages in the console and files\n" " -h, --help Show this help.\n" " --log=N Set the verbosity to a value between\n" " 0 (Debug) and 5 (Only Fatal messages)\n" " --root=DIR Path to add to the list of STK root directories.\n" " You can specify more than one by separating them\n" " with colons (:).\n" "\n" "You can visit SuperTuxKart's homepage at " "http://supertuxkart.sourceforge.net\n\n", CommandLine::getExecName().c_str() ); } // cmdLineHelp //============================================================================= /** For base options that modify the output (loglevel/color) or exit right * after being processed (version/help). */ int handleCmdLineOutputModifier() { if (CommandLine::has("--help") || CommandLine::has("-help") || CommandLine::has("-h")) { cmdLineHelp(); cleanUserConfig(); exit(0); } if(CommandLine::has("--version") || CommandLine::has("-v")) { Log::info("main", "=============================="); Log::info("main", "SuperTuxKart, %s.", STK_VERSION ) ; // IRRLICHT_VERSION_SVN Log::info("main", "Irrlicht version %i.%i.%i (%s)", IRRLICHT_VERSION_MAJOR , IRRLICHT_VERSION_MINOR, IRRLICHT_VERSION_REVISION, IRRLICHT_SDK_VERSION ); Log::info("main", "=============================="); cleanUserConfig(); exit(0); } int n; if(CommandLine::has("--log", &n)) Log::setLogLevel(n); if(CommandLine::has("--log=nocolor")) { Log::disableColor(); Log::verbose("main", "Colours disabled."); } if(CommandLine::has("--console")) UserConfigParams::m_log_errors_to_console=true; if(CommandLine::has("--no-console")) UserConfigParams::m_log_errors_to_console=false; return 0; } //============================================================================= /** For base options that don't need much to be inited (and, in some cases, * that need to be read before initing stuff) - it only assumes that * user config is loaded (necessary to check for blacklisted screen * resolutions), but nothing else (esp. not kart_properties_manager and * track_manager, since their search path might be extended by command * line options). */ int handleCmdLinePreliminary() { if(CommandLine::has("--gamepad-visualisation") || // only BE CommandLine::has("--gamepad-visualization") ) // both AE and BE UserConfigParams::m_gamepad_visualisation=true; if(CommandLine::has("--debug=memory")) UserConfigParams::m_verbosity |= UserConfigParams::LOG_MEMORY; if(CommandLine::has("--debug=addons")) UserConfigParams::m_verbosity |= UserConfigParams::LOG_ADDONS; if(CommandLine::has("--debug=mgui")) UserConfigParams::m_verbosity |= UserConfigParams::LOG_GUI; if(CommandLine::has("--debug=flyable")) UserConfigParams::m_verbosity |= UserConfigParams::LOG_FLYABLE; if(CommandLine::has("--debug=mist")) UserConfigParams::m_verbosity |= UserConfigParams::LOG_MISC; if(CommandLine::has("--debug=all") ) UserConfigParams::m_verbosity |= UserConfigParams::LOG_ALL; if(CommandLine::has("--online")) MainMenuScreen::m_enable_online=true; std::string s; if(CommandLine::has("--stk-config", &s)) { stk_config->load(file_manager->getAsset(s)); Log::info("main", "STK config will be read from %s.",s.c_str()); } if(CommandLine::has("--trackdir", &s)) TrackManager::addTrackSearchDir(s); if(CommandLine::has("--kartdir", &s)) KartPropertiesManager::addKartSearchDir(s); if(CommandLine::has("--no-graphics") || CommandLine::has("-l")) { ProfileWorld::disableGraphics(); UserConfigParams::m_log_errors_to_console=true; } if(CommandLine::has("--screensize", &s) || CommandLine::has("-s", &s)) { //Check if fullscreen and new res is blacklisted int width, height; if (sscanf(s.c_str(), "%dx%d", &width, &height) == 2) { // Reassemble the string in case that the original width or // height contained a leading 0 std::ostringstream o; o << width << "x" << height; std::string res = o.str(); if (!UserConfigParams::m_fullscreen || std::find(UserConfigParams::m_blacklist_res.begin(), UserConfigParams::m_blacklist_res.end(),res) == UserConfigParams::m_blacklist_res.end()) { UserConfigParams::m_prev_width = UserConfigParams::m_width = width; UserConfigParams::m_prev_height = UserConfigParams::m_height = height; Log::verbose("main", "You choose to use %dx%d.", (int)UserConfigParams::m_width, (int)UserConfigParams::m_height ); } else Log::warn("main", "Resolution %s has been blacklisted, so " "it is not available!", res.c_str()); } else { Log::fatal("main", "Error: --screensize argument must be " "given as WIDTHxHEIGHT"); } } if(CommandLine::has("--fullscreen") || CommandLine::has("-f")) { // Check that current res is not blacklisted std::ostringstream o; o << UserConfigParams::m_width << "x" << UserConfigParams::m_height; std::string res = o.str(); if (std::find(UserConfigParams::m_blacklist_res.begin(), UserConfigParams::m_blacklist_res.end(),res) == UserConfigParams::m_blacklist_res.end()) UserConfigParams::m_fullscreen = true; else Log::warn("main", "Resolution %s has been blacklisted, so it " "is not available!", res.c_str()); } if(CommandLine::has("--windowed") || CommandLine::has("-w")) UserConfigParams::m_fullscreen = false; // Enable loading grand prix from local directory if(CommandLine::has("--add-gp-dir", &s)) { // Ensure that the path ends with a / if (s[s.size()] == '/') UserConfigParams::m_additional_gp_directory = s; else UserConfigParams::m_additional_gp_directory = s + "/"; Log::info("main", "Additional Grand Prix's will be loaded from %s", UserConfigParams::m_additional_gp_directory.c_str()); } int n; if(CommandLine::has("--xmas", &n)) UserConfigParams::m_xmas_mode = n; if (CommandLine::has("--easter", &n)) UserConfigParams::m_easter_ear_mode = n; return 0; } // handleCmdLinePreliminary // ============================================================================ /** Handles command line options. * \param argc Number of command line options */ int handleCmdLine() { // Some generic variables used in scanning: int n; std::string s; bool try_login = false; irr::core::stringw login, password; if (CommandLine::has("--unit-testing")) UserConfigParams::m_unit_testing = true; if (CommandLine::has("--gamepad-debug")) UserConfigParams::m_gamepad_debug=true; if (CommandLine::has("--keyboard-debug")) UserConfigParams::m_keyboard_debug = true; if (CommandLine::has("--wiimote-debug")) UserConfigParams::m_wiimote_debug = true; if(CommandLine::has("--tutorial-debug")) UserConfigParams::m_tutorial_debug = true; if(CommandLine::has( "--track-debug",&n)) UserConfigParams::m_track_debug=n; if(CommandLine::has( "--track-debug")) UserConfigParams::m_track_debug=1; if(CommandLine::has("--material-debug")) UserConfigParams::m_material_debug = true; if(CommandLine::has("--ftl-debug")) UserConfigParams::m_ftl_debug = true; if(CommandLine::has("--slipstream-debug")) UserConfigParams::m_slipstream_debug=true; if(CommandLine::has("--rendering-debug")) UserConfigParams::m_rendering_debug=true; if(CommandLine::has("--ai-debug")) AIBaseController::enableDebug(); if (CommandLine::has("--fps-debug")) UserConfigParams::m_fps_debug = true; if(UserConfigParams::m_artist_debug_mode) { if(CommandLine::has("--camera-wheel-debug")) UserConfigParams::m_camera_debug=2; if(CommandLine::has("--camera-debug")) UserConfigParams::m_camera_debug=1; if(CommandLine::has("--camera-kart-debug")) UserConfigParams::m_camera_debug=4; if(CommandLine::has("--physics-debug")) UserConfigParams::m_physics_debug=1; if(CommandLine::has("--check-debug")) UserConfigParams::m_check_debug=true; } // Networking command lines NetworkConfig::get()-> setMaxPlayers(UserConfigParams::m_server_max_players); if(CommandLine::has("--server", &s)) { NetworkConfig::get()->setServerName(core::stringw(s.c_str())); NetworkConfig::get()->setIsServer(true); NetworkConfig::get()->setIsWAN(); STKHost::create(); Log::info("main", "Creating a WAN server '%s'.", s.c_str()); } if (CommandLine::has("--lan-server", &s)) { NetworkConfig::get()->setServerName(core::stringw(s.c_str())); NetworkConfig::get()->setIsServer(true); NetworkConfig::get()->setIsLAN(); STKHost::create(); Log::info("main", "Creating a LAN server '%s'.", s.c_str()); } if (CommandLine::has("--server-password", &s)) { NetworkConfig::get()->setPassword(s); } if(CommandLine::has("--max-players", &n)) UserConfigParams::m_server_max_players=n; if(CommandLine::has("--login", &s) ) { login = s.c_str(); try_login = true; } // --login if(CommandLine::has("--password", &s)) password = s.c_str(); // Race parameters if(CommandLine::has("--kartsize-debug")) { for(unsigned int i=0; igetNumberOfKarts(); i++) { const KartProperties *km = kart_properties_manager->getKartById(i); Log::info("main", "%s:\t%swidth: %f length: %f height: %f " "mesh-buffer count %d", km->getIdent().c_str(), (km->getIdent().size()<7) ? "\t" : "", km->getMasterKartModel().getWidth(), km->getMasterKartModel().getLength(), km->getMasterKartModel().getHeight(), km->getMasterKartModel().getModel() ->getMeshBufferCount()); } // for i } // --kartsize-debug if(CommandLine::has("--kart", &s)) { const KartProperties *prop = kart_properties_manager->getKart(s); if (prop) { UserConfigParams::m_default_kart = s; // if a player was added with -N, change its kart. // Otherwise, nothing to do, kart choice will be picked // up upon player creation. if (StateManager::get()->activePlayerCount() > 0) { race_manager->setPlayerKart(0, s); } Log::verbose("main", "You chose to use kart '%s'.", s.c_str()); } else { Log::warn("main", "Kart '%s' not found, ignored.", s.c_str()); } } // if --kart if(CommandLine::has("--ai", &s)) { const std::vector l=StringUtils::split(std::string(s),','); race_manager->setDefaultAIKartList(l); // Add 1 for the player kart race_manager->setNumKarts((int)l.size()+1); } // --ai if(CommandLine::has( "--mode", &s) || CommandLine::has( "--difficulty", &s)) { int n = atoi(s.c_str()); if(n<0 || n>RaceManager::DIFFICULTY_LAST) Log::warn("main", "Invalid difficulty '%s' - ignored.\n", s.c_str()); else race_manager->setDifficulty(RaceManager::Difficulty(n)); } // --mode if(CommandLine::has("--type", &n)) { switch (n) { case 0: race_manager->setMinorMode(RaceManager::MINOR_MODE_NORMAL_RACE); break; case 1: race_manager->setMinorMode(RaceManager::MINOR_MODE_TIME_TRIAL); break; case 2: race_manager->setMinorMode(RaceManager::MINOR_MODE_FOLLOW_LEADER); break; default: Log::warn("main", "Invalid race type '%d' - ignored.", n); } } // --type if(CommandLine::has("--track", &s) || CommandLine::has("-t", &s)) { race_manager->setTrack(s); Log::verbose("main", "You choose to start in track '%s'.", s.c_str()); Track* t = track_manager->getTrack(s); if (!t) { Log::warn("main", "Can't find track named '%s'.", s.c_str()); } else if (t->isArena()) { //if it's arena, don't create ai karts const std::vector l; race_manager->setDefaultAIKartList(l); // Add 1 for the player kart race_manager->setNumKarts(1); race_manager->setMinorMode(RaceManager::MINOR_MODE_3_STRIKES); } else if (t->isSoccer()) { //if it's soccer, don't create ai karts const std::vector l; race_manager->setDefaultAIKartList(l); // Add 1 for the player kart race_manager->setNumKarts(1); race_manager->setMinorMode(RaceManager::MINOR_MODE_SOCCER); } } // --track if(CommandLine::has("--gp", &s)) { race_manager->setMajorMode(RaceManager::MAJOR_MODE_GRAND_PRIX); const GrandPrixData *gp = grand_prix_manager->getGrandPrix(s); if (!gp) { Log::warn("main", "There is no GP named '%s'.", s.c_str()); return 0; } race_manager->setGrandPrix(*gp); } // --gp if(CommandLine::has("--numkarts", &n) ||CommandLine::has("-k", &n)) { UserConfigParams::m_num_karts = n; if(UserConfigParams::m_num_karts > stk_config->m_max_karts) { Log::warn("main", "Number of karts reset to maximum number %d.", stk_config->m_max_karts); UserConfigParams::m_num_karts = stk_config->m_max_karts; } race_manager->setNumKarts( UserConfigParams::m_num_karts ); Log::verbose("main", "%d karts will be used.", (int)UserConfigParams::m_num_karts); } // --numkarts if(CommandLine::has( "--no-start-screen") || CommandLine::has("-N") ) UserConfigParams::m_no_start_screen = true; if(CommandLine::has("--race-now") || CommandLine::has("-R")) { UserConfigParams::m_no_start_screen = true; UserConfigParams::m_race_now = true; } // --race-now if(CommandLine::has("--laps", &s)) { int laps = atoi(s.c_str()); if (laps < 0) { Log::error("main", "Invalid number of laps: %s.\n", s.c_str()); return 0; } else { Log::verbose("main", "You choose to have %d laps.", laps); race_manager->setNumLaps(laps); } } // --laps if(CommandLine::has("--profile-laps", &n)) { if (n < 0) { Log::error("main", "Invalid number of profile-laps: %i.", n ); return 0; } else { Log::verbose("main", "Profiling %d laps.",n); UserConfigParams::m_no_start_screen = true; ProfileWorld::setProfileModeLaps(n); race_manager->setNumLaps(n); } } // --profile-laps if(CommandLine::has("--profile-time", &n)) { Log::verbose("main", "Profiling: %d seconds.", n); UserConfigParams::m_no_start_screen = true; ProfileWorld::setProfileModeTime((float)n); race_manager->setNumLaps(999999); // profile end depends on time } // --profile-time if(CommandLine::has("--with-profile") ) { // Set default profile mode of 1 lap if we haven't already set one if (!ProfileWorld::isProfileMode()) { UserConfigParams::m_no_start_screen = true; ProfileWorld::setProfileModeLaps(1); race_manager->setNumLaps(1); } } // --with-profile if(CommandLine::has("--history", &n)) { history->doReplayHistory( (History::HistoryReplayMode)n); // Force the no-start screen flag, since this initialises // the player structures correctly. UserConfigParams::m_no_start_screen = true; } // --history=%d if(CommandLine::has("--history")) // handy default for --history=1 { history->doReplayHistory(History::HISTORY_POSITION); // Force the no-start screen flag, since this initialises // the player structures correctly. UserConfigParams::m_no_start_screen = true; } // --history // Demo mode if(CommandLine::has("--demo-mode", &s)) { float t = 0; StringUtils::fromString(s, t); DemoWorld::enableDemoMode(t); // The default number of laps is taken from ProfileWorld and // is 0. So set a more useful default for demo mode. DemoWorld::setNumLaps(2); } // --demo-mode if(CommandLine::has("--demo-laps", &n)) { // Note that we use a separate setting for demo mode to avoid the // problem that someone plays a game, and in further demos then // the wrong (i.e. last selected) number of laps would be used DemoWorld::setNumLaps(n); } // --demo-laps if(CommandLine::has("--demo-karts", &n)) { // Note that we use a separate setting for demo mode to avoid the // problem that someone plays a game, and in further demos then // the wrong (i.e. last selected) number of karts would be used DemoWorld::setNumKarts(n); } // --demo-karts if(CommandLine::has("--demo-tracks", &s)) DemoWorld::setTracks(StringUtils::split(s,',')); #ifdef ENABLE_WIIUSE if(CommandLine::has("--wii")) WiimoteManager::enable(); #endif #ifdef __APPLE__ // on OS X, sometimes the Finder will pass a -psn* something parameter // to the application --> ignore it CommandLine::has("-psn"); #endif CommandLine::reportInvalidParameters(); if(ProfileWorld::isProfileMode()) { UserConfigParams::m_sfx = false; // Disable sound effects UserConfigParams::m_music = false;// and music when profiling } if (try_login) { irr::core::stringw s; Online::XMLRequest* request = PlayerManager::requestSignIn(login, password); if (request->isSuccess()) { Log::info("Main", "Logged in from command line."); } } return 1; } // handleCmdLine //============================================================================= /** Initialises the minimum number of managers to get access to user_config. */ void initUserConfig() { file_manager = new FileManager(); user_config = new UserConfig(); // needs file_manager user_config->loadConfig(); // Some parts of the file manager needs user config (paths for models // depend on artist debug flag). So init the rest of the file manager // after reading the user config file. file_manager->init(); if (UserConfigParams::m_language.toString() != "system") { #ifdef WIN32 std::string s=std::string("LANGUAGE=") +UserConfigParams::m_language.c_str(); _putenv(s.c_str()); #else setenv("LANGUAGE", UserConfigParams::m_language.c_str(), 1); #endif } translations = new Translations(); // needs file_manager stk_config = new STKConfig(); // in case of --stk-config // command line parameters } // initUserConfig //============================================================================= void initRest() { stk_config->load(file_manager->getAsset("stk_config.xml")); irr_driver = new IrrDriver(); StkTime::init(); // grabs the timer object from the irrlicht device // Now create the actual non-null device in the irrlicht driver irr_driver->initDevice(); // Init GUI IrrlichtDevice* device = irr_driver->getDevice(); video::IVideoDriver* driver = device->getVideoDriver(); if (UserConfigParams::m_gamepad_visualisation) { gamepadVisualisation(); exit(0); } GUIEngine::init(device, driver, StateManager::get()); // This only initialises the non-network part of the addons manager. The // online section of the addons manager will be initialised from a // separate thread running in network http. addons_manager = new AddonsManager(); Online::ProfileManager::create(); // The request manager will start the login process in case of a saved // session, so we need to read the main data from the players.xml file. // The rest will be read later (since the rest needs the unlock- and // achievement managers to be created, which can only be created later). PlayerManager::create(); Online::RequestManager::get()->startNetworkThread(); NewsManager::get(); // this will create the news manager music_manager = new MusicManager(); SFXManager::create(); // The order here can be important, e.g. KartPropertiesManager needs // defaultKartProperties, which are defined in stk_config. history = new History (); ReplayPlay::create(); ReplayRecorder::create(); material_manager = new MaterialManager (); track_manager = new TrackManager (); kart_properties_manager = new KartPropertiesManager(); projectile_manager = new ProjectileManager (); powerup_manager = new PowerupManager (); attachment_manager = new AttachmentManager (); highscore_manager = new HighscoreManager (); // The maximum texture size can not be set earlier, since // e.g. the background image needs to be loaded in high res. irr_driver->setMaxTextureSize(); KartPropertiesManager::addKartSearchDir( file_manager->getAddonsFile("karts/")); track_manager->addTrackSearchDir( file_manager->getAddonsFile("tracks/")); { XMLNode characteristicsNode(file_manager->getAsset("kart_characteristics.xml")); kart_properties_manager->loadCharacteristics(&characteristicsNode); } track_manager->loadTrackList(); music_manager->addMusicToTracks(); GUIEngine::addLoadingIcon(irr_driver->getTexture(FileManager::GUI, "notes.png" ) ); grand_prix_manager = new GrandPrixManager (); // Consistency check for challenges, and enable all challenges // that have all prerequisites fulfilled grand_prix_manager->checkConsistency(); GUIEngine::addLoadingIcon( irr_driver->getTexture(FileManager::GUI, "cup_gold.png" ) ); race_manager = new RaceManager (); // default settings for Quickstart race_manager->setNumPlayers(1); race_manager->setNumLaps (3); race_manager->setMajorMode (RaceManager::MAJOR_MODE_SINGLE); race_manager->setMinorMode (RaceManager::MINOR_MODE_NORMAL_RACE); race_manager->setDifficulty( (RaceManager::Difficulty)(int)UserConfigParams::m_difficulty); if (!track_manager->getTrack(UserConfigParams::m_last_track)) UserConfigParams::m_last_track.revertToDefaults(); race_manager->setTrack(UserConfigParams::m_last_track); } // initRest //============================================================================= void askForInternetPermission() { if (UserConfigParams::m_internet_status != Online::RequestManager::IPERM_NOT_ASKED) return; class ConfirmServer : public MessageDialog::IConfirmDialogListener { public: virtual void onConfirm() { // Typically internet is disabled here (just better safe // than sorry). If internet should be allowed, the news // manager needs to be started (which in turn activates // the addons manager). bool need_to_start_news_manager = UserConfigParams::m_internet_status != Online::RequestManager::IPERM_ALLOWED; UserConfigParams::m_internet_status = Online::RequestManager::IPERM_ALLOWED; if (need_to_start_news_manager) NewsManager::get()->init(false); GUIEngine::ModalDialog::dismiss(); } // onConfirm // -------------------------------------------------------- virtual void onCancel() { UserConfigParams::m_internet_status = Online::RequestManager::IPERM_NOT_ALLOWED; GUIEngine::ModalDialog::dismiss(); } // onCancel }; // ConfirmServer GUIEngine::ModalDialog *dialog = new MessageDialog(_("SuperTuxKart may connect to a server " "to download add-ons and notify you of updates. We also collect " "anonymous hardware statistics to help with the development of STK. " "Please read our privacy policy at http://privacy.supertuxkart.net. " "Would you like this feature to be enabled? (To change this setting " "at a later time, go to options, select tab " "'User Interface', and edit \"Connect to the " "Internet\" and \"Send anonymous HW statistics\")."), MessageDialog::MESSAGE_DIALOG_YESNO, new ConfirmServer(), true, true, 0.7f, 0.7f); GUIEngine::DialogQueue::get()->pushDialog(dialog, false); } // askForInternetPermission //============================================================================= #if defined(WIN32) && defined(_MSC_VER) #ifdef DEBUG #pragma comment(linker, "/SUBSYSTEM:console") #else #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") #endif #endif // ---------------------------------------------------------------------------- int main(int argc, char *argv[] ) { CommandLine::init(argc, argv); CrashReporting::installHandlers(); srand(( unsigned ) time( 0 )); try { std::string s; handleCmdLineOutputModifier(); if(CommandLine::has("--root", &s)) FileManager::addRootDirs(s); if (CommandLine::has("--stdout", &s)) FileManager::setStdoutName(s); // Init the minimum managers so that user config exists, then // handle all command line options that do not need (or must // not have) other managers initialised: initUserConfig(); handleCmdLinePreliminary(); initRest(); input_manager = new InputManager (); #ifdef ENABLE_WIIUSE wiimote_manager = new WiimoteManager(); #endif // Get into menu mode initially. input_manager->setMode(InputManager::MENU); main_loop = new MainLoop(); material_manager->loadMaterial(); GUIEngine::addLoadingIcon( irr_driver->getTexture(FileManager::GUI, "options_video.png")); kart_properties_manager -> loadAllKarts (); handleXmasMode(); handleEasterEarMode(); // Needs the kart and track directories to load potential challenges // in those dirs, so it can only be created after reading tracks // and karts. unlock_manager = new UnlockManager(); AchievementsManager::create(); // Reading the rest of the player data needs the unlock manager to // initialise the game slots of all players and the AchievementsManager // to initialise the AchievementsStatus, so it is done only now. PlayerManager::get()->initRemainingData(); GUIEngine::addLoadingIcon( irr_driver->getTexture(FileManager::GUI, "gui_lock.png" ) ); projectile_manager->loadData(); // Both item_manager and powerup_manager load models and therefore // textures from the model directory. To avoid reading the // materials.xml twice, we do this here once for both: file_manager->pushTextureSearchPath(file_manager->getAsset(FileManager::MODEL,"")); const std::string materials_file = file_manager->getAsset(FileManager::MODEL,"materials.xml"); if(materials_file!="") { // Some of the materials might be needed later, so just add // them all permanently (i.e. as shared). Adding them temporary // will actually not be possible: powerup_manager adds some // permanent icon materials, which would (with the current // implementation) make the temporary materials permanent anyway. material_manager->addSharedMaterial(materials_file); } Referee::init(); powerup_manager->loadAllPowerups(); ItemManager::loadDefaultItemMeshes(); GUIEngine::addLoadingIcon( irr_driver->getTexture(FileManager::GUI, "gift.png") ); file_manager->popTextureSearchPath(); attachment_manager->loadModels(); GUIEngine::addLoadingIcon( irr_driver->getTexture(FileManager::GUI, "banana.png") ); //handleCmdLine() needs InitTuxkart() so it can't be called first if(!handleCmdLine()) exit(0); addons_manager->checkInstalledAddons(); // Load addons.xml to get info about addons even when not // allowed to access the internet if (UserConfigParams::m_internet_status != Online::RequestManager::IPERM_ALLOWED) { std::string xml_file = file_manager->getAddonsFile("addonsX.xml"); if (file_manager->fileExists(xml_file)) { try { const XMLNode *xml = new XMLNode(xml_file); addons_manager->initAddons(xml); } catch (std::runtime_error& e) { Log::warn("Addons", "Exception thrown when initializing addons manager : %s", e.what()); } } } if(UserConfigParams::m_unit_testing) { runUnitTests(); exit(0); } if (!ProfileWorld::isNoGraphics() && GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_DRIVER_RECENT_ENOUGH)) { if (UserConfigParams::m_old_driver_popup) { MessageDialog *dialog = new MessageDialog(_("Your driver version is too old. Please install " "the latest video drivers."), /*from queue*/ true); GUIEngine::DialogQueue::get()->pushDialog(dialog); } Log::warn("OpenGL", "Driver is too old!"); } else if (!CVS->isGLSL()) { if (UserConfigParams::m_old_driver_popup) { MessageDialog *dialog = new MessageDialog(_("Your OpenGL version appears to be too old. Please verify " "if an update for your video driver is available. SuperTuxKart requires OpenGL 3.1 or better."), /*from queue*/ true); GUIEngine::DialogQueue::get()->pushDialog(dialog); } Log::warn("OpenGL", "OpenGL version is too old!"); } // Note that on the very first run of STK internet status is set to // "not asked", so the report will only be sent in the next run. if(UserConfigParams::m_internet_status==Online::RequestManager::IPERM_ALLOWED) { HardwareStats::reportHardwareStats(); } if(!UserConfigParams::m_no_start_screen) { // If there is a current player, it was saved in the config file, // so we immediately start the main menu (unless it was requested // to always show the login screen). Otherwise show the login // screen first. if(PlayerManager::getCurrentPlayer() && ! UserConfigParams::m_always_show_login_screen) { MainMenuScreen::getInstance()->push(); } else { UserScreen::getInstance()->push(); // If there is no player, push the RegisterScreen on top of // the login screen. This way on first start players are // forced to create a player. if (PlayerManager::get()->getNumPlayers() == 0) { RegisterScreen::getInstance()->push(); RegisterScreen::getInstance()->setParent(UserScreen::getInstance()); } } #ifdef ENABLE_WIIUSE // Show a dialog to allow connection of wiimotes. */ if(WiimoteManager::isEnabled()) { wiimote_manager->askUserToConnectWiimotes(); } #endif askForInternetPermission(); } else { setupRaceStart(); // Go straight to the race StateManager::get()->enterGameState(); } // If an important news message exists it is shown in a popup dialog. const core::stringw important_message = NewsManager::get()->getImportantMessage(); if(important_message!="") { new MessageDialog(important_message, MessageDialog::MESSAGE_DIALOG_OK, NULL, true); } // if important_message // Replay a race // ============= if(history->replayHistory()) { // This will setup the race manager etc. history->Load(); race_manager->setupPlayerKartInfo(); race_manager->startNew(false); main_loop->run(); // well, actually run() will never return, since // it exits after replaying history (see history::GetNextDT()). // So the next line is just to make this obvious here! exit(-3); } // Not replaying // ============= if(!ProfileWorld::isProfileMode()) { if(UserConfigParams::m_no_start_screen) { // Quickstart (-N) // =============== // all defaults are set in InitTuxkart() race_manager->setupPlayerKartInfo(); race_manager->startNew(false); } } else // profile { // Profiling // ========= race_manager->setMajorMode (RaceManager::MAJOR_MODE_SINGLE); race_manager->setupPlayerKartInfo(); race_manager->startNew(false); } main_loop->run(); } // try catch (std::exception &e) { Log::error("main", "Exception caught : %s.",e.what()); Log::error("main", "Aborting SuperTuxKart."); } /* Program closing...*/ #ifdef ENABLE_WIIUSE if(wiimote_manager) delete wiimote_manager; #endif // If the window was closed in the middle of a race, remove players, // so we don't crash later when StateManager tries to access input devices. StateManager::get()->resetActivePlayers(); if(input_manager) delete input_manager; // if early crash avoid delete NULL if(NetworkConfig::get()->isNetworking() && STKHost::existHost()) STKHost::get()->abort(); cleanSuperTuxKart(); #ifdef DEBUG MemoryLeaks::checkForLeaks(); #endif #ifndef WIN32 if (user_config) //close logfiles { Log::closeOutputFiles(); #endif fclose(stderr); fclose(stdout); #ifndef WIN32 } #endif delete file_manager; return 0 ; } // main // ============================================================================ #ifdef WIN32 //routine for running under windows int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { return main(__argc, __argv); } #endif //============================================================================= /** Frees all manager and their associated memory. */ static void cleanSuperTuxKart() { delete main_loop; if(Online::RequestManager::isRunning()) Online::RequestManager::get()->stopNetworkThread(); // Stop music (this request will go into the sfx manager queue, so it needs // to be done before stopping the thread). music_manager->stopMusic(); SFXManager::get()->stopThread(); irr_driver->updateConfigIfRelevant(); AchievementsManager::destroy(); Referee::cleanup(); if(race_manager) delete race_manager; if(grand_prix_manager) delete grand_prix_manager; if(highscore_manager) delete highscore_manager; if(attachment_manager) delete attachment_manager; ItemManager::removeTextures(); if(powerup_manager) delete powerup_manager; if(projectile_manager) delete projectile_manager; if(kart_properties_manager) delete kart_properties_manager; if(track_manager) delete track_manager; if(material_manager) delete material_manager; if(history) delete history; ReplayPlay::destroy(); ReplayRecorder::destroy(); delete ParticleKindManager::get(); PlayerManager::destroy(); if(unlock_manager) delete unlock_manager; Online::ProfileManager::destroy(); GUIEngine::DialogQueue::deallocate(); // Now finish shutting down objects which a separate thread. The // RequestManager has been signaled to shut down as early as possible, // the NewsManager thread should have finished quite early on anyway. // But still give them some additional time to finish. It avoids a // race condition where a thread might access the file manager after it // was deleted (in cleanUserConfig below), but before STK finishes and // the os takes all threads down. if(!NewsManager::get()->waitForReadyToDeleted(2.0f)) { Log::info("Thread", "News manager not stopping, exiting anyway."); } NewsManager::deallocate(); if(!Online::RequestManager::get()->waitForReadyToDeleted(5.0f)) { Log::info("Thread", "Request Manager not aborting in time, aborting."); } Online::RequestManager::deallocate(); if (!SFXManager::get()->waitForReadyToDeleted(2.0f)) { Log::info("Thread", "SFXManager not stopping, exiting anyway."); } SFXManager::destroy(); // Music manager can not be deleted before the sfx thread is stopped // (since sfx commands can contain music information, which are // deleted by the music manager). delete music_manager; // The addons manager might still be called from a currenty running request // in the request manager, so it can not be deleted earlier. if(addons_manager) delete addons_manager; // FIXME: do we need to wait for threads there, can they be // moved further up? ServersManager::deallocate(); if(NetworkConfig::get()->isNetworking() && STKHost::existHost()) STKHost::destroy(); cleanUserConfig(); StateManager::deallocate(); GUIEngine::EventHandler::deallocate(); } // cleanSuperTuxKart //============================================================================= /** * Frees all the memory of initUserConfig() */ static void cleanUserConfig() { if(stk_config) delete stk_config; if(translations) delete translations; if (user_config) { // In case that abort is triggered before user_config exists if (UserConfigParams::m_crashed) UserConfigParams::m_crashed = false; user_config->saveConfig(); delete user_config; } if(irr_driver) delete irr_driver; } // cleanUserConfig //============================================================================= void runUnitTests() { Log::info("UnitTest", "Starting unit testing"); Log::info("UnitTest", "====================="); Log::info("UnitTest", "- GraphicsRestrictions"); GraphicsRestrictions::unitTesting(); Log::info("UnitTest", " - NetworkString"); NetworkString::unitTesting(); Log::info("UnitTest", " - Easter detection"); // Test easter mode: in 2015 Easter is 5th of April - check with 0 days // before and after int saved_easter_mode = UserConfigParams::m_easter_ear_mode; UserConfigParams::m_easter_ear_mode = 0; // disable always on or off mode assert( isEasterMode( 5, 4, 2015, 0)); assert(!isEasterMode( 4, 4, 2015, 0)); assert(!isEasterMode( 6, 4, 2015, 0)); assert( isEasterMode( 1, 4, 2018, 0)); assert( isEasterMode(27, 3, 2016, 0)); // Check days before/after assert( isEasterMode( 2, 4, 2015, 3)); assert(!isEasterMode( 1, 4, 2015, 3)); assert( isEasterMode( 8, 4, 2015, 3)); assert(!isEasterMode( 9, 4, 2015, 3)); // Check if going to previous month is handled correctly: 1/4/2018 assert( isEasterMode(27, 3, 2018, 5)); assert(!isEasterMode(26, 3, 2018, 5)); assert( isEasterMode( 6, 4, 2018, 5)); assert(!isEasterMode( 7, 4, 2018, 5)); // Check if going to previous month is handled correctly: 1/4/2018 assert( isEasterMode( 1, 4, 2016, 5)); assert(!isEasterMode( 2, 4, 2016, 5)); assert( isEasterMode(22, 3, 2016, 5)); assert(!isEasterMode(21, 3, 2016, 5)); UserConfigParams::m_easter_ear_mode = saved_easter_mode; Log::info("UnitTest", "====================="); Log::info("UnitTest", "Testing successful "); Log::info("UnitTest", "====================="); } // runUnitTests