/* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2012 The Mana Developers * * This file is part of The Mana Client. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * 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, see . */ #include "client.h" #include "main.h" #include "chatlogger.h" #include "configuration.h" #include "emoteshortcut.h" #include "event.h" #include "game.h" #include "itemshortcut.h" #include "keyboardconfig.h" #include "log.h" #include "playerrelations.h" #include "sound.h" #include "gui/changeemaildialog.h" #include "gui/changepassworddialog.h" #include "gui/charselectdialog.h" #include "gui/connectiondialog.h" #include "gui/gui.h" #include "gui/logindialog.h" #include "gui/okdialog.h" #include "gui/quitdialog.h" #include "gui/register.h" #include "gui/sdlinput.h" #include "gui/serverdialog.h" #include "gui/setup.h" #include "gui/unregisterdialog.h" #include "gui/updaterwindow.h" #include "gui/worldselectdialog.h" #include "gui/widgets/button.h" #include "gui/widgets/desktop.h" #include "net/charhandler.h" #include "net/gamehandler.h" #include "net/generalhandler.h" #include "net/logindata.h" #include "net/loginhandler.h" #include "net/net.h" #include "net/worldinfo.h" #include "resources/chardb.h" #include "resources/hairdb.h" #include "resources/image.h" #include "resources/itemdb.h" #include "resources/resourcemanager.h" #include "resources/theme.h" #include "resources/userpalette.h" #include "resources/settingsmanager.h" #include "utils/filesystem.h" #include "utils/gettext.h" #include "utils/mkdir.h" #if defined(_WIN32) || defined(__APPLE__) #include "utils/specialfolder.h" #endif #include "utils/stringutils.h" #include "utils/time.h" #include #ifdef _WIN32 #include #include #endif #include #include #include // TODO: Get rid of these globals std::string errorMessage; LoginData loginData; Config config; /**< Global settings (config.xml) */ Configuration branding; /**< XML branding information reader */ Configuration paths; /**< XML default paths information reader */ ChatLogger *chatLogger; /**< Chat log object */ KeyboardConfig keyboard; UserPalette *userPalette; Graphics *graphics; ItemDB *itemDb; /**< Items info database */ HairDB hairDB; /**< Hair styles and colors info database */ Sound sound; volatile int fps = 0; /**< Frames counted in the last second */ volatile int frame_count = 0; /**< Counts the frames during one second */ /** * Updates fps. * Called every seconds by SDL_AddTimer() */ static Uint32 nextSecond(Uint32 interval, void *param) { fps = frame_count; frame_count = 0; return interval; } bool isDoubleClick(int selected) { static Timer timer; static int lastSelected = -1; if (selected == lastSelected && !timer.passed()) return true; timer.set(500); lastSelected = selected; return false; } /** * A simple but effective frame rate limiter. * Based on FPSmanager in SDL2_gfx. */ void FpsManager::limitFps(int fpsLimit) { if (fpsLimit <= 0) return; const auto ticks = SDL_GetTicks(); // Need to reset if the FPS limit changes if (mBaseTicks > 0 && mFpsLimit == fpsLimit) { ++mFrameCount; const auto targetTicks = mBaseTicks + mFrameCount * 1000 / mFpsLimit; // Make sure ticks hasn't wrapped if (ticks > mBaseTicks && targetTicks > ticks) { SDL_Delay(targetTicks - ticks); return; } } mFpsLimit = fpsLimit; mFrameCount = 0; mBaseTicks = ticks; } Client *Client::mInstance = nullptr; Client::Client(const Options &options): mOptions(options), mStateAfterOkDialog(mState) { assert(!mInstance); mInstance = this; // Set default values for configuration files branding.setDefaultValues(getBrandingDefaults()); paths.setDefaultValues(getPathsDefaults()); // Load branding information if (!options.brandingPath.empty()) { branding.init(options.brandingPath); } initRootDir(); initHomeDir(); initConfiguration(); // Configure logger Log::init(); Log::setLogFile(mLocalDataDir + "/mana.log"); Log::setLogToStandardOut(config.logToStandardOut); Log::info("%s", FULL_VERSION); chatLogger = new ChatLogger; if (options.chatLogDir.empty()) chatLogger->setLogDir(mLocalDataDir + "/logs/"); else chatLogger->setLogDir(options.chatLogDir); initScreenshotDir(); #if SDL_VERSION_ATLEAST(2, 24, 0) SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); #endif // Initialize SDL Log::info("Initializing SDL..."); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { Log::critical(strprintf("Could not initialize SDL: %s", SDL_GetError())); } atexit(SDL_Quit); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); if (!FS::setWriteDir(mLocalDataDir)) { Log::critical(strprintf("%s couldn't be set as write directory! " "Exiting.", mLocalDataDir.c_str())); } #if defined __APPLE__ mPackageDir = getResourcesLocation() + "/data"; #else mPackageDir = PKG_DATADIR "data"; #endif ResourceManager::addToSearchPath(mPackageDir, false); ResourceManager::addToSearchPath("data", false); // Add branding/data to PhysFS search path if (!options.brandingPath.empty()) { std::string path = options.brandingPath; // Strip blah.mana from the path #ifdef _WIN32 int loc1 = path.find_last_of('/'); int loc2 = path.find_last_of('\\'); int loc = std::max(loc1, loc2); #else int loc = path.find_last_of('/'); #endif if (loc > 0) ResourceManager::addToSearchPath(path.substr(0, loc + 1) + "data", false); } // Add the main data directories to our PhysicsFS search path if (!options.dataPath.empty()) ResourceManager::addToSearchPath(options.dataPath, false); // Add the local data directory to PhysicsFS search path ResourceManager::addToSearchPath(mLocalDataDir, false); bool useOpenGL = !mOptions.noOpenGL && config.opengl; // Set up the transparency option for low CPU when not using OpenGL. if (!useOpenGL && config.disableTransparency) Image::SDLdisableTransparency(); VideoSettings videoSettings; videoSettings.windowMode = config.windowMode; videoSettings.width = config.screenWidth; videoSettings.height = config.screenHeight; videoSettings.userScale = config.scale; videoSettings.vsync = config.vsync; videoSettings.openGL = useOpenGL; // Try to set the desired video mode and create the graphics context graphics = mVideo.initialize(videoSettings); SDL_SetWindowTitle(mVideo.window(), branding.getValue("appName", "Mana").c_str()); std::string iconFile = branding.getValue("appIcon", "icons/mana"); #ifdef _WIN32 iconFile += ".ico"; #else iconFile += ".png"; #endif iconFile = ResourceManager::getPath(iconFile); Log::info("Loading icon from file: %s", iconFile.c_str()); #ifdef _WIN32 static SDL_SysWMinfo pInfo; SDL_GetWindowWMInfo(mVideo.window(), &pInfo); // Attempt to load icon from .ico file HICON icon = (HICON) LoadImage(NULL, iconFile.c_str(), IMAGE_ICON, 64, 64, LR_LOADFROMFILE); // If it's failing, we load the default resource file. if (!icon) icon = LoadIcon(GetModuleHandle(NULL), "A"); if (icon) SetClassLongPtr(pInfo.info.win.window, GCLP_HICON, (LONG_PTR) icon); #else mIcon = IMG_Load(iconFile.c_str()); if (mIcon) { SDL_SetWindowIcon(mVideo.window(), mIcon); } #endif // Initialize the item and emote shortcuts. itemShortcut = new ItemShortcut; emoteShortcut = new EmoteShortcut; gui = new Gui(graphics, Theme::prepareThemePath()); // Initialize sound engine try { if (config.sound) sound.init(); sound.setSfxVolume(config.sfxVolume); sound.setNotificationsVolume(config.notificationsVolume); sound.setMusicVolume(config.musicVolume); } catch (const char *err) { mState = STATE_ERROR; errorMessage = err; Log::warn("%s", err); } // Initialize keyboard keyboard.init(); // Initialise player relations player_relations.init(); userPalette = new UserPalette; setupWindow = new Setup; sound.playMusic(branding.getStringValue("loginMusic")); // Initialize default server mCurrentServer.hostname = options.serverName; mCurrentServer.port = options.serverPort; mCurrentServer.type = options.serverType; loginData.username = options.username; loginData.password = options.password; loginData.remember = config.remember; loginData.registerLogin = false; if (mCurrentServer.type == ServerType::Unknown && mCurrentServer.port != 0) { mCurrentServer.type = ServerInfo::defaultServerTypeForPort(mCurrentServer.port); } if (mCurrentServer.type == ServerType::Unknown) { mCurrentServer.type = ServerInfo::parseType( branding.getValue("defaultServerType", "tmwathena")); } if (mCurrentServer.port == 0) { const uint16_t defaultPort = ServerInfo::defaultPortForServerType(mCurrentServer.type); mCurrentServer.port = static_cast( branding.getValue("defaultPort", defaultPort)); } const bool noServerList = branding.getValue("onlineServerList", std::string()).empty(); if (mCurrentServer.hostname.empty() && noServerList) { mCurrentServer.hostname = branding.getValue("defaultServer", std::string()); } if (chatLogger) chatLogger->setServerName(mCurrentServer.hostname); if (loginData.username.empty() && loginData.remember) loginData.username = config.username; if (mState != STATE_ERROR) { // If a server was passed on the command line, or branding // provides a server and a blank server list, we skip the // server selection dialog. mState = mCurrentServer.isValid() ? STATE_CONNECT_SERVER : STATE_CHOOSE_SERVER; } // Initialize seconds counter mSecondsCounterId = SDL_AddTimer(1000, nextSecond, nullptr); // Initialize PlayerInfo PlayerInfo::init(); } Client::~Client() { SDL_RemoveTimer(mSecondsCounterId); // Unload XML databases SettingsManager::unload(); CharDB::unload(); delete itemDb; ActorSprite::unload(); // Before config.write() since it writes the shortcuts to the config delete itemShortcut; delete emoteShortcut; delete gui; // Shutdown libxml xmlCleanupParser(); // Shutdown sound sound.close(); ResourceManager::deleteInstance(); SDL_FreeSurface(mIcon); Log::info("Quitting"); delete userPalette; XML::Writer writer(mConfigDir + "/config.xml"); if (writer.isValid()) serialize(writer, config); mInstance = nullptr; } int Client::exec() { Time::beginFrame(); // Prevent startup lag influencing the first frame while (mState != STATE_EXIT) { // Handle SDL events SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: mState = STATE_EXIT; break; case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: handleWindowSizeChanged(event.window.data1, event.window.data2); break; } break; case SDL_KEYDOWN: if (keyboard.isEnabled()) { const int tKey = keyboard.getKeyIndex(event.key.keysym.sym); if (tKey == KeyboardConfig::KEY_WINDOW_SETUP) { setupWindow->setVisible(!setupWindow->isVisible()); if (setupWindow->isVisible()) setupWindow->requestMoveToTop(); continue; } } if (setupWindow->isVisible() && keyboard.getNewKeyIndex() > KeyboardConfig::KEY_NO_VALUE) { keyboard.setNewKey(event.key.keysym.sym); keyboard.callbackNewKey(); keyboard.setNewKeyIndex(KeyboardConfig::KEY_NO_VALUE); continue; } // Check whether the game will handle the event if (mGame && mGame->keyDownEvent(event.key)) continue; break; } // Push input to GUI when not used try { guiInput->pushInput(event); } catch (gcn::Exception e) { const char *err = e.getMessage().c_str(); Log::warn("Guichan input exception: %s", err); } } update(); } Net::unload(); return 0; } void Client::update() { Time::beginFrame(); mVideo.updateWindowSize(); checkGraphicsSize(); // Let the game handle continuous input while it is active if (mGame) mGame->handleInput(); if (Net::getGeneralHandler()) Net::getGeneralHandler()->flushNetwork(); gui->logic(); if (mGame) mGame->logic(); sound.logic(); // Update the screen when application is active, delay otherwise. if (isActive()) { frame_count++; gui->draw(); mVideo.present(); mFpsManager.limitFps(config.fpsLimit); } else { mFpsManager.limitFps(10); } // TODO: Add connect timeouts if (mState == STATE_CONNECT_GAME && Net::getGameHandler()->isConnected()) { Net::getLoginHandler()->disconnect(); } else if (mState == STATE_CONNECT_SERVER && mOldState == STATE_CONNECT_SERVER && Net::getLoginHandler()->isConnected()) { mState = STATE_LOGIN; } else if (mState == STATE_WORLD_SELECT && mOldState == STATE_UPDATE) { if (Net::getLoginHandler()->getWorlds().size() < 2) { mState = STATE_LOGIN; } } else if (mOldState == STATE_START || (mOldState == STATE_GAME && mState != STATE_GAME)) { auto *top = static_cast(gui->getTop()); mDesktop = new Desktop; top->add(mDesktop); mSetupButton = new Button("", "Setup", this); mSetupButton->setButtonPopupText(_("Setup")); mSetupButton->setButtonIcon("button-icon-setup.png"); mSetupButton->setPosition(top->getWidth() - mSetupButton->getWidth() - 3, 3); top->add(mSetupButton); mDesktop->setSize(graphics->getWidth(), graphics->getHeight()); } if (mState == STATE_SWITCH_LOGIN && mOldState == STATE_GAME) { Net::getGameHandler()->disconnect(); } if (mState != mOldState) { { Event event(Event::StateChange); event.setInt("oldState", mOldState); event.setInt("newState", mState); event.trigger(Event::ClientChannel); } if (mOldState == STATE_GAME) { delete mGame; mGame = nullptr; } mOldState = mState; // Get rid of the dialog of the previous state if (mCurrentDialog) { delete mCurrentDialog; mCurrentDialog = nullptr; } // State has changed, while the quitDialog was active, it might // not be correct anymore if (mQuitDialog) mQuitDialog->scheduleDelete(); switch (mState) { case STATE_CHOOSE_SERVER: Log::info("State: CHOOSE SERVER"); // Don't allow an alpha opacity // lower than the default value gui->getTheme()->setMinimumOpacity(0.8f); mCurrentDialog = new ServerDialog(&mCurrentServer, mConfigDir); break; case STATE_CONNECT_SERVER: Log::info("State: CONNECT SERVER"); Net::connectToServer(mCurrentServer); mCurrentDialog = new ConnectionDialog( _("Connecting to server"), STATE_SWITCH_SERVER); break; case STATE_LOGIN: Log::info("State: LOGIN"); // Don't allow an alpha opacity // lower than the default value gui->getTheme()->setMinimumOpacity(0.8f); if (mOptions.username.empty() || mOptions.password.empty()) { mCurrentDialog = new LoginDialog(&loginData); } else { mState = STATE_LOGIN_ATTEMPT; // Clear the password so that when login fails, the // dialog will show up next time. mOptions.password.clear(); } break; case STATE_LOGIN_ATTEMPT: Log::info("State: LOGIN ATTEMPT"); accountLogin(&loginData); mCurrentDialog = new ConnectionDialog( _("Logging in"), STATE_SWITCH_SERVER); break; case STATE_WORLD_SELECT: Log::info("State: WORLD SELECT"); { Worlds worlds = Net::getLoginHandler()->getWorlds(); if (worlds.empty()) { // Trust that the netcode knows what it's doing mState = STATE_UPDATE; } else if (worlds.size() == 1 || mOptions.chooseDefault) { Net::getLoginHandler()->chooseServer(0); mState = STATE_UPDATE; } else { mCurrentDialog = new WorldSelectDialog(std::move(worlds)); } } break; case STATE_WORLD_SELECT_ATTEMPT: Log::info("State: WORLD SELECT ATTEMPT"); mCurrentDialog = new ConnectionDialog( _("Entering game world"), STATE_WORLD_SELECT); break; case STATE_UPDATE: Log::info("State: UPDATE"); if (mOptions.skipUpdate) { mState = STATE_LOAD_DATA; } else if (initUpdatesDir()) { mCurrentDialog = new UpdaterWindow(mUpdateHost, mLocalDataDir + "/" + mUpdatesDir, mOptions.dataPath.empty()); } break; case STATE_LOAD_DATA: Log::info("State: LOAD DATA"); // If another data path has been set, // we don't load any other files... if (mOptions.dataPath.empty()) { // Add customdata directory ResourceManager::searchAndAddArchives( "customdata/", "zip", false); } // TODO remove this as soon as inventoryhandler stops using this event Event::trigger(Event::ClientChannel, Event::LoadingDatabases); // Load XML databases CharDB::load(); delete itemDb; switch (Net::getNetworkType()) { case ServerType::TmwAthena: itemDb = new TmwAthena::TaItemDB; break; case ServerType::ManaServ: itemDb = new ManaServ::ManaServItemDB; break; default: // Nothing itemDb = nullptr; break; } assert(itemDb); // load settings.xml SettingsManager::load(); ActorSprite::load(); mDesktop->reloadWallpaper(); mState = STATE_GET_CHARACTERS; break; case STATE_GET_CHARACTERS: Log::info("State: GET CHARACTERS"); Net::getCharHandler()->requestCharacters(); mCurrentDialog = new ConnectionDialog( _("Requesting characters"), STATE_SWITCH_SERVER); break; case STATE_CHAR_SELECT: Log::info("State: CHAR SELECT"); // Don't allow an alpha opacity // lower than the default value gui->getTheme()->setMinimumOpacity(0.8f); mCurrentDialog = new CharSelectDialog(&loginData); if (!((CharSelectDialog*) mCurrentDialog)->selectByName( mOptions.character, CharSelectDialog::Choose)) { ((CharSelectDialog*) mCurrentDialog)->selectByName( config.lastCharacter, mOptions.chooseDefault ? CharSelectDialog::Choose : CharSelectDialog::Focus); } // Choosing character on the command line should work only // once, clear it so that 'switch character' works. mOptions.character.clear(); mOptions.chooseDefault = false; break; case STATE_CONNECT_GAME: Log::info("State: CONNECT GAME"); Net::getGameHandler()->connect(); mCurrentDialog = new ConnectionDialog( _("Connecting to the game server"), Net::getNetworkType() == ServerType::TmwAthena ? STATE_CHOOSE_SERVER : STATE_SWITCH_CHARACTER); break; case STATE_CHANGE_MAP: Log::info("State: CHANGE_MAP"); Net::getGameHandler()->connect(); mCurrentDialog = new ConnectionDialog( _("Changing game servers"), STATE_SWITCH_CHARACTER); break; case STATE_GAME: Log::info("Memorizing selected character %s", local_player->getName().c_str()); config.lastCharacter = local_player->getName(); // Fade out logon-music here too to give the desired effect // of "flowing" into the game. sound.fadeOutMusic(1000); // Allow any alpha opacity gui->getTheme()->setMinimumOpacity(0.0f); delete mSetupButton; delete mDesktop; mSetupButton = nullptr; mDesktop = nullptr; mCurrentDialog = nullptr; Log::info("State: GAME"); mGame = new Game; break; case STATE_LOGIN_ERROR: Log::info("State: LOGIN ERROR"); showErrorDialog(errorMessage, STATE_LOGIN); break; case STATE_ACCOUNTCHANGE_ERROR: Log::info("State: ACCOUNT CHANGE ERROR"); showErrorDialog(errorMessage, STATE_CHAR_SELECT); break; case STATE_REGISTER_PREP: Log::info("State: REGISTER_PREP"); Net::getLoginHandler()->getRegistrationDetails(); mCurrentDialog = new ConnectionDialog( _("Requesting registration details"), STATE_LOGIN); break; case STATE_REGISTER: Log::info("State: REGISTER"); mCurrentDialog = new RegisterDialog(&loginData); break; case STATE_REGISTER_ATTEMPT: Log::info("Username is %s", loginData.username.c_str()); Net::getLoginHandler()->registerAccount(&loginData); loginData.password.clear(); break; case STATE_CHANGEPASSWORD: Log::info("State: CHANGE PASSWORD"); mCurrentDialog = new ChangePasswordDialog(&loginData); break; case STATE_CHANGEPASSWORD_ATTEMPT: Log::info("State: CHANGE PASSWORD ATTEMPT"); Net::getLoginHandler()->changePassword(loginData.username, loginData.password, loginData.newPassword); break; case STATE_CHANGEPASSWORD_SUCCESS: Log::info("State: CHANGE PASSWORD SUCCESS"); showOkDialog(_("Password Change"), _("Password changed successfully!"), STATE_CHAR_SELECT); loginData.password.clear(); loginData.newPassword.clear(); break; case STATE_CHANGEEMAIL: Log::info("State: CHANGE EMAIL"); mCurrentDialog = new ChangeEmailDialog(&loginData); break; case STATE_CHANGEEMAIL_ATTEMPT: Log::info("State: CHANGE EMAIL ATTEMPT"); Net::getLoginHandler()->changeEmail(loginData.email); break; case STATE_CHANGEEMAIL_SUCCESS: Log::info("State: CHANGE EMAIL SUCCESS"); showOkDialog(_("Email Change"), _("Email changed successfully!"), STATE_CHAR_SELECT); break; case STATE_UNREGISTER: Log::info("State: UNREGISTER"); mCurrentDialog = new UnRegisterDialog(&loginData); break; case STATE_UNREGISTER_ATTEMPT: Log::info("State: UNREGISTER ATTEMPT"); Net::getLoginHandler()->unregisterAccount( loginData.username, loginData.password); break; case STATE_UNREGISTER_SUCCESS: Log::info("State: UNREGISTER SUCCESS"); Net::getLoginHandler()->disconnect(); showOkDialog(_("Unregister Successful"), _("Farewell, come back any time..."), STATE_CHOOSE_SERVER); loginData.clear(); break; case STATE_SWITCH_SERVER: Log::info("State: SWITCH SERVER"); Net::getLoginHandler()->disconnect(); Net::getGameHandler()->disconnect(); mCurrentServer.hostname.clear(); mState = STATE_CHOOSE_SERVER; break; case STATE_SWITCH_LOGIN: Log::info("State: SWITCH LOGIN"); Net::getLoginHandler()->disconnect(); mState = STATE_CONNECT_SERVER; break; case STATE_SWITCH_CHARACTER: Log::info("State: SWITCH CHARACTER"); // Done with game Net::getGameHandler()->disconnect(); mState = STATE_GET_CHARACTERS; break; case STATE_LOGOUT_ATTEMPT: Log::info("State: LOGOUT ATTEMPT"); // TODO break; case STATE_WAIT: Log::info("State: WAIT"); break; case STATE_EXIT: Log::info("State: EXIT"); break; case STATE_FORCE_QUIT: Log::info("State: FORCE QUIT"); mState = STATE_EXIT; break; case STATE_ERROR: Log::info("State: ERROR"); Log::error("%s", errorMessage.c_str()); showErrorDialog(errorMessage, STATE_CHOOSE_SERVER); Net::getGameHandler()->disconnect(); break; default: mState = STATE_FORCE_QUIT; break; } } } void Client::showOkDialog(const std::string &title, const std::string &message, State state) { auto *okDialog = new OkDialog(title, message); okDialog->addActionListener(this); mStateAfterOkDialog = state; } void Client::showErrorDialog(const std::string &message, State state) { showOkDialog(_("Error"), message, state); } void Client::action(const gcn::ActionEvent &event) { Window *window = nullptr; if (event.getId() == "Setup") window = setupWindow; if (window) { window->setVisible(!window->isVisible()); if (window->isVisible()) window->requestMoveToTop(); } // If this came from the OkDialog used by showOkDialog if (event.getId() == "ok") mState = mStateAfterOkDialog; } void Client::initRootDir() { mRootDir = FS::getBaseDir(); #ifdef _WIN32 std::string portableName = mRootDir + "portable.xml"; struct stat statbuf; if (!stat(portableName.c_str(), &statbuf) && S_ISREG(statbuf.st_mode)) { std::string dir; Configuration portable; portable.init(portableName); Log::info("Portable file: %s", portableName.c_str()); if (mOptions.localDataDir.empty()) { dir = portable.getValue("dataDir", ""); if (!dir.empty()) { mOptions.localDataDir = mRootDir + dir; Log::info("Portable data dir: %s", mOptions.localDataDir.c_str()); } } if (mOptions.configDir.empty()) { dir = portable.getValue("configDir", ""); if (!dir.empty()) { mOptions.configDir = mRootDir + dir; Log::info("Portable config dir: %s", mOptions.configDir.c_str()); } } if (mOptions.screenshotDir.empty()) { dir = portable.getValue("screenshotDir", ""); if (!dir.empty()) { mOptions.screenshotDir = mRootDir + dir; Log::info("Portable screenshot dir: %s", mOptions.screenshotDir.c_str()); } } } #endif } /** * Initializes the home directory. On UNIX and FreeBSD, ~/.mana is used. On * Windows and other systems we use the current working directory. */ void Client::initHomeDir() { mLocalDataDir = mOptions.localDataDir; if (mLocalDataDir.empty()) { #if defined __HAIKU__ mLocalDataDir = FS::getUserDir(); mLocalDataDir += "/config/data/Mana"; #elif defined _WIN32 mLocalDataDir = getSpecialFolderLocation(FOLDERID_LocalAppData); if (mLocalDataDir.empty()) mLocalDataDir = FS::getUserDir(); mLocalDataDir += "/Mana"; #else mLocalDataDir = FS::getPrefDir("manasource.org", "mana"); #endif } if (mkdir_r(mLocalDataDir.c_str())) { Log::critical(strprintf(_("%s doesn't exist and can't be created! " "Exiting."), mLocalDataDir.c_str())); } mConfigDir = mOptions.configDir; if (mConfigDir.empty()) { const std::string app = branding.getValue("appShort", "manasource"); #ifdef __APPLE__ mConfigDir = mLocalDataDir + "/" + app; #elif defined __HAIKU__ mConfigDir = FS::getPrefDir("manasource.org", "Mana"); mConfigDir += app; #elif defined _WIN32 mConfigDir = FS::getPrefDir("Mana", app.c_str()); #else mConfigDir = std::string(FS::getUserDir()) + ".config/mana/" + app; #endif } if (mkdir_r(mConfigDir.c_str())) { Log::critical(strprintf(_("%s doesn't exist and can't be created! " "Exiting."), mConfigDir.c_str())); } } /** * Initialize configuration. */ void Client::initConfiguration() { // Fill configuration with defaults config.updatehost = branding.getValue("defaultUpdateHost", std::string()); const std::string configPath = mConfigDir + "/config.xml"; XML::Document doc(configPath, false); if (doc.rootNode() && doc.rootNode().name() == "configuration") deserialize(doc.rootNode(), config); else Log::info("Couldn't read configuration file: %s", configPath.c_str()); } /** * Parse the update host and determine the updates directory * Then verify that the directory exists (creating if needed). */ bool Client::initUpdatesDir() { // Determine which source to use for the update host if (!mOptions.updateHost.empty()) mUpdateHost = mOptions.updateHost; else if (!loginData.updateHost.empty()) mUpdateHost = loginData.updateHost; else mUpdateHost = config.updatehost; // Remove any trailing slashes at the end of the URL while (!mUpdateHost.empty() && mUpdateHost.back() == '/') mUpdateHost.pop_back(); if (mUpdateHost.empty()) { Log::info("No update host provided"); mUpdatesDir.clear(); mState = STATE_LOAD_DATA; return false; } mUpdatesDir = "updates/" + getDirectoryFromURL(mUpdateHost); Log::info("Update host: %s", mUpdateHost.c_str()); Log::info("Updates dir: %s", mUpdatesDir.c_str()); // Verify that the updates directory exists. Create if necessary. if (!FS::isDirectory(mUpdatesDir)) { if (!FS::mkdir(mUpdatesDir)) { #if defined _WIN32 std::string newDir = mLocalDataDir + "\\" + mUpdatesDir; std::string::size_type loc = newDir.find("/", 0); while (loc != std::string::npos) { newDir.replace(loc, 1, "\\"); loc = newDir.find("/", loc); } if (!CreateDirectory(newDir.c_str(), 0) && GetLastError() != ERROR_ALREADY_EXISTS) { Log::error("%s can't be made, but doesn't exist!", newDir.c_str()); errorMessage = strprintf(_("Error creating updates directory!\n(%s)"), newDir.c_str()); mState = STATE_ERROR; } #else Log::error("%s/%s can't be made, but doesn't exist!", mLocalDataDir.c_str(), mUpdatesDir.c_str()); errorMessage = strprintf(_("Error creating updates directory!\n(%s/%s)"), mLocalDataDir.c_str(), mUpdatesDir.c_str()); mState = STATE_ERROR; return false; #endif } } return true; } void Client::initScreenshotDir() { if (!mOptions.screenshotDir.empty()) { mScreenshotDir = mOptions.screenshotDir; } else if (mScreenshotDir.empty()) { #ifdef _WIN32 mScreenshotDir = getSpecialFolderLocation(FOLDERID_Pictures); if (mScreenshotDir.empty()) mScreenshotDir = getSpecialFolderLocation(FOLDERID_Desktop); #else mScreenshotDir = std::string(FS::getUserDir()) + "Desktop"; #endif if (config.useScreenshotDirectorySuffix) { std::string screenshotSuffix = config.screenshotDirectorySuffix; if (screenshotSuffix.empty()) screenshotSuffix = branding.getValue("appShort", "Mana"); if (!screenshotSuffix.empty()) { mScreenshotDir += "/" + screenshotSuffix; config.screenshotDirectorySuffix = screenshotSuffix; } } } } void Client::accountLogin(LoginData *loginData) { Log::info("Username is %s", loginData->username.c_str()); // Send login infos if (loginData->registerLogin) Net::getLoginHandler()->registerAccount(loginData); else Net::getLoginHandler()->loginAccount(loginData); // Clear the password, avoids auto login when returning to login loginData->password.clear(); // TODO This is not the best place to save the config, but at least better // than the login gui window if (loginData->remember) config.username = loginData->username; config.remember = loginData->remember; } void Client::handleWindowSizeChanged(int width, int height) { // Store the new size in the configuration. config.screenWidth = width; config.screenHeight = height; } void Client::checkGraphicsSize() { const int width = graphics->getWidth(); const int height = graphics->getHeight(); if (!gui->videoResized(width, height)) return; if (mDesktop) mDesktop->setSize(width, height); if (mSetupButton) mSetupButton->setPosition(width - mSetupButton->getWidth() - 3, 3); if (mGame) mGame->videoResized(width, height); } bool Client::isActive() { return !(SDL_GetWindowFlags(getVideo().window()) & SDL_WINDOW_MINIMIZED); } bool Client::hasInputFocus() { return SDL_GetWindowFlags(getVideo().window()) & SDL_WINDOW_INPUT_FOCUS; } bool Client::hasMouseFocus() { return SDL_GetWindowFlags(getVideo().window()) & SDL_WINDOW_MOUSE_FOCUS; }