diff options
author | Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> | 2010-02-21 20:40:07 +0100 |
---|---|---|
committer | Chuck Miller <shadowmil@gmail.com> | 2010-02-21 17:05:40 -0500 |
commit | c8b0d1e56f27c3141895d28b2fc768afffe7bb2d (patch) | |
tree | 0a03458836badee3e1b0da13a0721c9261e7fa86 /src/client.cpp | |
parent | 204a14c91bbe4436eb3b26bebf30cbe5669bdd1a (diff) | |
download | mana-c8b0d1e56f27c3141895d28b2fc768afffe7bb2d.tar.gz mana-c8b0d1e56f27c3141895d28b2fc768afffe7bb2d.tar.bz2 mana-c8b0d1e56f27c3141895d28b2fc768afffe7bb2d.tar.xz mana-c8b0d1e56f27c3141895d28b2fc768afffe7bb2d.zip |
Made tick counter and framerate limiter work during login sequence
Much code was moved from main() to the new Client::exec(). This new
event loop now integrates with the Game class, so that the tick counter
and framerate limiter apply universally.
The Client class is also responsible for some things that used to be
global variables.
Mantis-issue: ...
Diffstat (limited to 'src/client.cpp')
-rw-r--r-- | src/client.cpp | 1152 |
1 files changed, 1152 insertions, 0 deletions
diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 00000000..1f608fa6 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,1152 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 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 <http://www.gnu.org/licenses/>. + */ + +#include "client.h" +#include "main.h" + +#include "configuration.h" +#include "emoteshortcut.h" +#include "game.h" +#include "itemshortcut.h" +#include "keyboardconfig.h" +#ifdef USE_OPENGL +#include "openglgraphics.h" +#endif +#include "playerrelations.h" +#include "sound.h" +#include "statuseffect.h" +#include "units.h" + +#include "gui/changeemaildialog.h" +#include "gui/changepassworddialog.h" +#include "gui/charselectdialog.h" +#include "gui/connectiondialog.h" +#include "gui/gui.h" +#include "gui/skin.h" +#include "gui/login.h" +#include "gui/okdialog.h" +#include "gui/palette.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/updatewindow.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/colordb.h" +#include "resources/emotedb.h" +#include "resources/image.h" +#include "resources/itemdb.h" +#include "resources/monsterdb.h" +#include "resources/npcdb.h" +#include "resources/resourcemanager.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" + +#ifdef __APPLE__ +#include <CoreFoundation/CFBundle.h> +#endif + +#include <physfs.h> +#include <SDL_image.h> + +#ifdef WIN32 +#include <SDL_syswm.h> +#else +#include <cerrno> +#include <sys/stat.h> +#endif + +#include <cassert> + +/** + * Tells the max tick value, + * setting it back to zero (and start again). + */ +static const int MAX_TICK_VALUE = 10000; + +static const int defaultSfxVolume = 100; +static const int defaultMusicVolume = 60; + +// TODO: Get rid fo these globals +std::string errorMessage; +ErrorListener errorListener; +LoginData loginData; + +Configuration config; /**< XML file configuration reader */ +Configuration branding; /**< XML branding information reader */ +Logger *logger; /**< Log object */ +KeyboardConfig keyboard; + +Palette *guiPalette; +Graphics *graphics; + +Sound sound; + +void ErrorListener::action(const gcn::ActionEvent &) +{ + Client::setState(STATE_CHOOSE_SERVER); +} + +volatile int tick_time; /**< Tick counter */ +volatile int fps = 0; /**< Frames counted in the last second */ +volatile int frame_count = 0; /**< Counts the frames during one second */ + +/** + * Advances game logic counter. + * Called every 10 milliseconds by SDL_AddTimer() + * @see MILLISECONDS_IN_A_TICK value + */ +Uint32 nextTick(Uint32 interval, void *param) +{ + tick_time++; + if (tick_time == MAX_TICK_VALUE) + tick_time = 0; + return interval; +} + +/** + * Updates fps. + * Called every seconds by SDL_AddTimer() + */ +Uint32 nextSecond(Uint32 interval, void *param) +{ + fps = frame_count; + frame_count = 0; + + return interval; +} + +/** + * @return the elapsed time in milliseconds + * between two tick values. + */ +int get_elapsed_time(int start_time) +{ + if (start_time <= tick_time) + return (tick_time - start_time) * MILLISECONDS_IN_A_TICK; + else + return (tick_time + (MAX_TICK_VALUE - start_time)) + * MILLISECONDS_IN_A_TICK; +} + + +// This anonymous namespace hides whatever is inside from other modules. +namespace { + +class AccountListener : public gcn::ActionListener +{ +public: + void action(const gcn::ActionEvent &) + { + Client::setState(STATE_CHAR_SELECT); + } +} accountListener; + +class LoginListener : public gcn::ActionListener +{ +public: + void action(const gcn::ActionEvent &) + { + Client::setState(STATE_LOGIN); + } +} loginListener; + +} // anonymous namespace + + +Client *Client::mInstance = 0; + +Client::Client(const Options &options): + options(options), + currentDialog(0), + quitDialog(0), + desktop(0), + setupButton(0), + state(STATE_CHOOSE_SERVER), + oldstate(STATE_START), + mLogicCounterId(0), + mSecondsCounterId(0), + mLimitFps(false) +{ + assert(!mInstance); + mInstance = this; + + // Load branding information + branding.init("data/branding.xml"); + + initHomeDir(options); + initScreenshotDir(options.screenshotDir); + + // Configure logger + logger = new Logger; + logger->setLogFile(homeDir + std::string("/mana.log")); + + // Log the mana version + logger->log("Mana %s", FULL_VERSION); + + initConfiguration(options); + logger->setLogToStandardOut(config.getValue("logToStandardOut", 0)); + + // Initialize SDL + logger->log("Initializing SDL..."); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) + { + logger->error(strprintf("Could not initialize SDL: %s", + SDL_GetError())); + } + atexit(SDL_Quit); + + SDL_EnableUNICODE(1); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + + SDL_WM_SetCaption(branding.getValue("appName", "Mana").c_str(), NULL); + + ResourceManager *resman = ResourceManager::getInstance(); + + if (!resman->setWriteDir(homeDir)) + { + logger->error(strprintf("%s couldn't be set as home directory! " + "Exiting.", homeDir.c_str())); + } + + // Add the user's homedir to PhysicsFS search path + resman->addToSearchPath(homeDir, false); + + // Add the main data directories to our PhysicsFS search path + if (!options.dataPath.empty()) + resman->addToSearchPath(options.dataPath, true); + resman->addToSearchPath("data", true); +#if defined __APPLE__ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); + char path[PATH_MAX]; + if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, + PATH_MAX)) + { + fprintf(stderr, "Can't find Resources directory\n"); + } + CFRelease(resourcesURL); + strncat(path, "/data", PATH_MAX - 1); + resman->addToSearchPath(path, true); +#else + resman->addToSearchPath(PKG_DATADIR "data", true); +#endif + +#ifdef WIN32 + static SDL_SysWMinfo pInfo; + SDL_GetWMInfo(&pInfo); + HICON icon = LoadIcon(GetModuleHandle(NULL), "A"); + if (icon) + { + SetClassLong(pInfo.window, GCL_HICON, (LONG) icon); + } +#else + icon = IMG_Load(resman->getPath( + branding.getValue("appIcon", "data/icons/mana.png")).c_str()); + if (icon) + { + SDL_SetAlpha(icon, SDL_SRCALPHA, SDL_ALPHA_OPAQUE); + SDL_WM_SetIcon(icon, NULL); + } +#endif + +#ifdef USE_OPENGL + bool useOpenGL = !options.noOpenGL && (config.getValue("opengl", 0) == 1); + + // Setup image loading for the right image format + Image::setLoadAsOpenGL(useOpenGL); + + // Create the graphics context + graphics = useOpenGL ? new OpenGLGraphics : new Graphics; +#else + // Create the graphics context + graphics = new Graphics; +#endif + + const int width = (int) config.getValue("screenwidth", defaultScreenWidth); + const int height = (int) config.getValue("screenheight", defaultScreenHeight); + const int bpp = 0; + const bool fullscreen = ((int) config.getValue("screen", 0) == 1); + const bool hwaccel = ((int) config.getValue("hwaccel", 0) == 1); + + // Try to set the desired video mode + if (!graphics->setVideoMode(width, height, bpp, fullscreen, hwaccel)) + { + logger->error(strprintf("Couldn't set %dx%dx%d video mode: %s", + width, height, bpp, SDL_GetError())); + } + + // Initialize for drawing + graphics->_beginDraw(); + + // Initialize the item shortcuts. + itemShortcut = new ItemShortcut; + + // Initialize the emote shortcuts. + emoteShortcut = new EmoteShortcut; + + gui = new Gui(graphics); + + // Initialize sound engine + try + { + if (config.getValue("sound", 0) == 1) + sound.init(); + + sound.setSfxVolume((int) config.getValue("sfxVolume", + defaultSfxVolume)); + sound.setMusicVolume((int) config.getValue("musicVolume", + defaultMusicVolume)); + } + catch (const char *err) + { + state = STATE_ERROR; + errorMessage = err; + logger->log("Warning: %s", err); + } + + // Initialize keyboard + keyboard.init(); + + // Initialise player relations + player_relations.init(); + + guiPalette = new Palette; + setupWindow = new Setup; + + sound.playMusic(branding.getValue("loginMusic", "Magick - Real.ogg")); + + // Initialize default server + currentServer.hostname = options.serverName; + currentServer.port = options.serverPort; + loginData.username = options.username; + loginData.password = options.password; + loginData.remember = config.getValue("remember", 0); + loginData.registerLogin = false; + + if (currentServer.hostname.empty()) + { + currentServer.hostname = branding.getValue("defaultServer", + "server.themanaworld.org").c_str(); + } + if (options.serverPort == 0) + { + currentServer.port = (short) branding.getValue("defaultPort", + DEFAULT_PORT); + } + if (loginData.username.empty() && loginData.remember) + loginData.username = config.getValue("username", ""); + + if (state != STATE_ERROR) + state = STATE_CHOOSE_SERVER; + + // Initialize logic and seconds counters + tick_time = 0; + mLogicCounterId = SDL_AddTimer(MILLISECONDS_IN_A_TICK, nextTick, NULL); + mSecondsCounterId = SDL_AddTimer(1000, nextSecond, NULL); + + // Initialize frame limiting + SDL_initFramerate(&mFpsManager); + config.addListener("fpslimit", this); + optionChanged("fpslimit"); +} + +Client::~Client() +{ + SDL_RemoveTimer(mLogicCounterId); + SDL_RemoveTimer(mSecondsCounterId); + + // Before config.write() since it writes the shortcuts to the config + delete itemShortcut; + delete emoteShortcut; + + config.write(); + + delete gui; + delete graphics; + + // Shutdown libxml + xmlCleanupParser(); + + // Shutdown sound + sound.close(); + + // Unload XML databases + ColorDB::unload(); + EmoteDB::unload(); + ItemDB::unload(); + MonsterDB::unload(); + NPCDB::unload(); + StatusEffect::unload(); + + ResourceManager::deleteInstance(); + + SDL_FreeSurface(icon); + + logger->log("Quitting"); + delete guiPalette; + delete logger; + + mInstance = 0; +} + +int Client::exec() +{ + int lastTickTime = tick_time; + + Game *game = 0; + SDL_Event event; + + while (state != STATE_EXIT) + { + bool handledEvents = false; + + if (game) + { + // Let the game handle the events while it is active + game->handleInput(); + } + else + { + // Handle SDL events + while (SDL_PollEvent(&event)) + { + handledEvents = true; + + switch (event.type) + { + case SDL_QUIT: + state = STATE_EXIT; + break; + + case SDL_KEYDOWN: + break; + } + + guiInput->pushInput(event); + } + } + + if (Net::getGeneralHandler()) + Net::getGeneralHandler()->flushNetwork(); + + while (get_elapsed_time(lastTickTime) > 0) + { + gui->logic(); + if (game) + game->logic(); + + ++lastTickTime; + } + + // This is done because at some point tick_time will wrap. + lastTickTime = tick_time; + + // Update the screen when application is active, delay otherwise. + if (SDL_GetAppState() & SDL_APPACTIVE) + { + frame_count++; + gui->draw(); + graphics->updateScreen(); + } + else + { + SDL_Delay(10); + } + + if (mLimitFps) + SDL_framerateDelay(&mFpsManager); + + + // TODO: Add connect timeouts + if (state == STATE_CONNECT_GAME && + Net::getGameHandler()->isConnected()) + { + Net::getLoginHandler()->disconnect(); + } + else if (state == STATE_CONNECT_SERVER && oldstate == STATE_CHOOSE_SERVER) + { + Net::connectToServer(currentServer); + } + else if (state == STATE_CONNECT_SERVER && + oldstate != STATE_CHOOSE_SERVER && + Net::getLoginHandler()->isConnected()) + { + state = STATE_LOGIN; + } + else if (state == STATE_WORLD_SELECT && oldstate == STATE_UPDATE) + { + if (Net::getLoginHandler()->getWorlds().size() < 2) + { + state = STATE_LOGIN; + } + } + else if (oldstate == STATE_START || + (oldstate == STATE_GAME && state != STATE_GAME)) + { + gcn::Container *top = static_cast<gcn::Container*>(gui->getTop()); + + desktop = new Desktop; + top->add(desktop); + setupButton = new Button(_("Setup"), "Setup", this); + setupButton->setPosition(top->getWidth() - setupButton->getWidth() + - 3, 3); + top->add(setupButton); + + int screenWidth = (int) config.getValue("screenwidth", + defaultScreenWidth); + int screenHeight = (int) config.getValue("screenheight", + defaultScreenHeight); + + desktop->setSize(screenWidth, screenHeight); + } + + if (state == STATE_SWITCH_LOGIN && oldstate == STATE_GAME) + { + Net::getGameHandler()->disconnect(); + } + + if (state != oldstate) + { + if (oldstate == STATE_GAME) + { + delete game; + game = 0; + } + + oldstate = state; + + // Get rid of the dialog of the previous state + if (currentDialog) + { + delete currentDialog; + currentDialog = NULL; + } + // State has changed, while the quitDialog was active, it might + // not be correct anymore + if (quitDialog) + quitDialog->scheduleDelete(); + + switch (state) + { + case STATE_CHOOSE_SERVER: + logger->log("State: CHOOSE SERVER"); + + // Allow changing this using a server choice dialog + // We show the dialog box only if the command-line + // options weren't set. + if (options.serverName.empty() && options.serverPort == 0) + { + // Don't allow an alpha opacity + // lower than the default value + SkinLoader::instance()->setMinimumOpacity(0.8f); + + currentDialog = new ServerDialog(¤tServer, + homeDir); + } + else + { + state = STATE_CONNECT_SERVER; + + // Reset options so that cancelling or connect + // timeout will show the server dialog. + options.serverName.clear(); + options.serverPort = 0; + } + break; + + case STATE_CONNECT_SERVER: + logger->log("State: CONNECT SERVER"); + currentDialog = new ConnectionDialog( + _("Connecting to server"), STATE_SWITCH_SERVER); + break; + + case STATE_LOGIN: + logger->log("State: LOGIN"); + // Don't allow an alpha opacity + // lower than the default value + SkinLoader::instance()->setMinimumOpacity(0.8f); + + if (options.username.empty() + || options.password.empty()) + { + currentDialog = new LoginDialog(&loginData); + } + else + { + state = STATE_LOGIN_ATTEMPT; + // Clear the password so that when login fails, the + // dialog will show up next time. + options.password.clear(); + } + break; + + case STATE_LOGIN_ATTEMPT: + logger->log("State: LOGIN ATTEMPT"); + accountLogin(&loginData); + currentDialog = new ConnectionDialog( + _("Logging in"), STATE_SWITCH_SERVER); + break; + + case STATE_WORLD_SELECT: + logger->log("State: WORLD SELECT"); + { + Worlds worlds = Net::getLoginHandler()->getWorlds(); + + if (worlds.size() == 0) + { + // Trust that the netcode knows what it's doing + state = STATE_UPDATE; + } + else if (worlds.size() == 1) + { + Net::getLoginHandler()->chooseServer(0); + state = STATE_UPDATE; + } + else + { + currentDialog = new WorldSelectDialog(worlds); + if (options.chooseDefault) + { + ((WorldSelectDialog*) currentDialog)->action( + gcn::ActionEvent(NULL, "ok")); + } + } + } + break; + + case STATE_WORLD_SELECT_ATTEMPT: + logger->log("State: WORLD SELECT ATTEMPT"); + currentDialog = new ConnectionDialog( + _("Entering game world"), STATE_WORLD_SELECT); + break; + + case STATE_UPDATE: + // Determine which source to use for the update host + if (!options.updateHost.empty()) + updateHost = options.updateHost; + else + updateHost = loginData.updateHost; + initUpdatesDir(); + + if (options.skipUpdate) + { + state = STATE_LOAD_DATA; + } + else + { + logger->log("State: UPDATE"); + currentDialog = new UpdaterWindow(updateHost, + homeDir + "/" + updatesDir,options.dataPath.empty()); + } + break; + + case STATE_LOAD_DATA: + logger->log("State: LOAD DATA"); + + // If another data path has been set, + // we don't load any other files... + if (options.dataPath.empty()) + { + // Add customdata directory + ResourceManager::getInstance()->searchAndAddArchives( + "customdata/", + "zip", + false); + } + + // Load XML databases + ColorDB::load(); + ItemDB::load(); + Being::load(); // Hairstyles + MonsterDB::load(); + NPCDB::load(); + EmoteDB::load(); + StatusEffect::load(); + Units::loadUnits(); + + desktop->reloadWallpaper(); + + state = STATE_GET_CHARACTERS; + break; + + case STATE_GET_CHARACTERS: + logger->log("State: GET CHARACTERS"); + Net::getCharHandler()->requestCharacters(); + currentDialog = new ConnectionDialog( + _("Requesting characters"), + STATE_SWITCH_SERVER); + break; + + case STATE_CHAR_SELECT: + logger->log("State: CHAR SELECT"); + // Don't allow an alpha opacity + // lower than the default value + SkinLoader::instance()->setMinimumOpacity(0.8f); + + currentDialog = new CharSelectDialog(&loginData); + + if (!((CharSelectDialog*) currentDialog)->selectByName( + options.character, CharSelectDialog::Choose)) + { + ((CharSelectDialog*) currentDialog)->selectByName( + config.getValue("lastCharacter", ""), + options.chooseDefault ? + CharSelectDialog::Choose : + CharSelectDialog::Focus); + } + + break; + + case STATE_CONNECT_GAME: + logger->log("State: CONNECT GAME"); + + Net::getGameHandler()->connect(); + currentDialog = new ConnectionDialog( + _("Connecting to the game server"), + STATE_SWITCH_CHARACTER); + break; + + case STATE_GAME: + logger->log("Memorizing selected character %s", + player_node->getName().c_str()); + config.setValue("lastCharacter", player_node->getName()); + + Net::getGameHandler()->inGame(); + + // Fade out logon-music here too to give the desired effect + // of "flowing" into the game. + sound.fadeOutMusic(1000); + + // Allow any alpha opacity + SkinLoader::instance()->setMinimumOpacity(-1.0f); + + delete setupButton; + delete desktop; + setupButton = NULL; + desktop = NULL; + + currentDialog = NULL; + + logger->log("State: GAME"); + game = new Game; + break; + + case STATE_LOGIN_ERROR: + logger->log("State: LOGIN ERROR"); + currentDialog = new OkDialog(_("Error"), errorMessage); + currentDialog->addActionListener(&loginListener); + currentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_ACCOUNTCHANGE_ERROR: + logger->log("State: ACCOUNT CHANGE ERROR"); + currentDialog = new OkDialog(_("Error"), errorMessage); + currentDialog->addActionListener(&accountListener); + currentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_REGISTER_PREP: + logger->log("State: REGISTER_PREP"); + Net::getLoginHandler()->getRegistrationDetails(); + currentDialog = new ConnectionDialog( + _("Requesting registration details"), STATE_LOGIN); + break; + + case STATE_REGISTER: + logger->log("State: REGISTER"); + currentDialog = new RegisterDialog(&loginData); + break; + + case STATE_REGISTER_ATTEMPT: + logger->log("Username is %s", loginData.username.c_str()); + Net::getLoginHandler()->registerAccount(&loginData); + break; + + case STATE_CHANGEPASSWORD: + logger->log("State: CHANGE PASSWORD"); + currentDialog = new ChangePasswordDialog(&loginData); + break; + + case STATE_CHANGEPASSWORD_ATTEMPT: + logger->log("State: CHANGE PASSWORD ATTEMPT"); + Net::getLoginHandler()->changePassword(loginData.username, + loginData.password, + loginData.newPassword); + break; + + case STATE_CHANGEPASSWORD_SUCCESS: + logger->log("State: CHANGE PASSWORD SUCCESS"); + currentDialog = new OkDialog(_("Password Change"), + _("Password changed successfully!")); + currentDialog->addActionListener(&accountListener); + currentDialog = NULL; // OkDialog deletes itself + loginData.password = loginData.newPassword; + loginData.newPassword = ""; + break; + + case STATE_CHANGEEMAIL: + logger->log("State: CHANGE EMAIL"); + currentDialog = new ChangeEmailDialog(&loginData); + break; + + case STATE_CHANGEEMAIL_ATTEMPT: + logger->log("State: CHANGE EMAIL ATTEMPT"); + Net::getLoginHandler()->changeEmail(loginData.email); + break; + + case STATE_CHANGEEMAIL_SUCCESS: + logger->log("State: CHANGE EMAIL SUCCESS"); + currentDialog = new OkDialog(_("Email Change"), + _("Email changed successfully!")); + currentDialog->addActionListener(&accountListener); + currentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_UNREGISTER: + logger->log("State: UNREGISTER"); + currentDialog = new UnRegisterDialog(&loginData); + break; + + case STATE_UNREGISTER_ATTEMPT: + logger->log("State: UNREGISTER ATTEMPT"); + Net::getLoginHandler()->unregisterAccount( + loginData.username, loginData.password); + break; + + case STATE_UNREGISTER_SUCCESS: + logger->log("State: UNREGISTER SUCCESS"); + Net::getLoginHandler()->disconnect(); + + currentDialog = new OkDialog(_("Unregister Successful"), + _("Farewell, come back any time...")); + loginData.clear(); + //The errorlistener sets the state to STATE_CHOOSE_SERVER + currentDialog->addActionListener(&errorListener); + currentDialog = NULL; // OkDialog deletes itself + break; + + case STATE_SWITCH_SERVER: + logger->log("State: SWITCH SERVER"); + + Net::getLoginHandler()->disconnect(); + Net::getGameHandler()->disconnect(); + + state = STATE_CHOOSE_SERVER; + break; + + case STATE_SWITCH_LOGIN: + logger->log("State: SWITCH LOGIN"); + + Net::getLoginHandler()->logout(); + + state = STATE_LOGIN; + break; + + case STATE_SWITCH_CHARACTER: + logger->log("State: SWITCH CHARACTER"); + + // Done with game + Net::getGameHandler()->disconnect(); + + Net::getCharHandler()->requestCharacters(); + break; + + case STATE_LOGOUT_ATTEMPT: + logger->log("State: LOGOUT ATTEMPT"); + // TODO + break; + + case STATE_WAIT: + logger->log("State: WAIT"); + break; + + case STATE_EXIT: + logger->log("State: EXIT"); + Net::unload(); + break; + + case STATE_FORCE_QUIT: + logger->log("State: FORCE QUIT"); + if (Net::getGeneralHandler()) + Net::getGeneralHandler()->unload(); + state = STATE_EXIT; + break; + + case STATE_ERROR: + logger->log("State: ERROR"); + currentDialog = new OkDialog(_("Error"), errorMessage); + currentDialog->addActionListener(&errorListener); + currentDialog = NULL; // OkDialog deletes itself + Net::getGameHandler()->disconnect(); + break; + + default: + state = STATE_FORCE_QUIT; + break; + } + } + } + + return 0; +} + +void Client::optionChanged(const std::string &name) +{ + const int fpsLimit = (int) config.getValue("fpslimit", 60); + mLimitFps = fpsLimit > 0; + if (mLimitFps) + SDL_setFramerate(&mFpsManager, fpsLimit); +} + +void Client::action(const gcn::ActionEvent &event) +{ + Window *window = 0; + + if (event.getId() == "Setup") + window = setupWindow; + + if (window) + { + window->setVisible(!window->isVisible()); + if (window->isVisible()) + window->requestMoveToTop(); + } +} + +/** + * 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(const Options &options) +{ + homeDir = options.homeDir; + + if (homeDir.empty()) + { +#ifdef __APPLE__ + // Use Application Directory instead of .mana + homeDir = std::string(PHYSFS_getUserDir()) + + "/Library/Application Support/" + + branding.getValue("appName", "Mana"); +#else + homeDir = std::string(PHYSFS_getUserDir()) + + "/." + branding.getValue("appShort", "mana"); +#endif + } +#if defined WIN32 + if (!CreateDirectory(homeDir.c_str(), 0) && + GetLastError() != ERROR_ALREADY_EXISTS) +#else + // Create home directory if it doesn't exist already + if ((mkdir(homeDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) && + (errno != EEXIST)) +#endif + { + logger->error(strprintf(_("%s doesn't exist and can't be created! " + "Exiting."), homeDir.c_str())); + } +} + +/** + * Initialize configuration. + */ +void Client::initConfiguration(const Options &options) +{ + // Fill configuration with defaults + logger->log("Initializing configuration..."); + std::string defaultHost = branding.getValue("defaultServer", + "server.themanaworld.org"); + int defaultPort = (int) branding.getValue("defaultPort", DEFAULT_PORT); + config.setValue("port", defaultPort); + config.setValue("hwaccel", false); +#if (defined __APPLE__ || defined WIN32) && defined USE_OPENGL + config.setValue("opengl", true); +#else + config.setValue("opengl", false); +#endif + config.setValue("screen", false); + config.setValue("sound", true); + config.setValue("guialpha", 0.8f); + config.setValue("remember", true); + config.setValue("sfxVolume", 100); + config.setValue("musicVolume", 60); + config.setValue("fpslimit", 60); + std::string defaultUpdateHost = branding.getValue("defaultUpdateHost", + "http://updates.themanaworld.org"); + config.setValue("updatehost", defaultUpdateHost); + config.setValue("customcursor", true); + config.setValue("ChatLogLength", 128); + + // Checking if the configuration file exists... otherwise create it with + // default options. + FILE *configFile = 0; + std::string configPath = options.configPath; + + if (configPath.empty()) + configPath = homeDir + "/config.xml"; + + configFile = fopen(configPath.c_str(), "r"); + + // If we can't read it, it doesn't exist ! + if (configFile == NULL) + { + // We reopen the file in write mode and we create it + configFile = fopen(configPath.c_str(), "wt"); + } + if (configFile == NULL) + { + logger->log("Can't create %s. Using defaults.", configPath.c_str()); + } + else + { + fclose(configFile); + config.init(configPath); + } +} + +/** + * Parse the update host and determine the updates directory + * Then verify that the directory exists (creating if needed). + */ +void Client::initUpdatesDir() +{ + std::stringstream updates; + + // If updatesHost is currently empty, fill it from config file + if (updateHost.empty()) + { + updateHost = + config.getValue("updatehost", "http://updates.themanaworld.org/"); + } + + // Remove any trailing slash at the end of the update host + if (updateHost.at(updateHost.size() - 1) == '/') + updateHost.resize(updateHost.size() - 1); + + // Parse out any "http://" or "ftp://", and set the updates directory + size_t pos; + pos = updateHost.find("://"); + if (pos != updateHost.npos) + { + if (pos + 3 < updateHost.length()) + { + updates << "updates/" << updateHost.substr(pos + 3); + updatesDir = updates.str(); + } + else + { + logger->log("Error: Invalid update host: %s", updateHost.c_str()); + errorMessage = strprintf(_("Invalid update host: %s"), + updateHost.c_str()); + state = STATE_ERROR; + } + } + else + { + logger->log("Warning: no protocol was specified for the update host"); + updates << "updates/" << updateHost; + updatesDir = updates.str(); + } + + ResourceManager *resman = ResourceManager::getInstance(); + + // Verify that the updates directory exists. Create if necessary. + if (!resman->isDirectory("/" + updatesDir)) + { + if (!resman->mkdir("/" + updatesDir)) + { +#if defined WIN32 + std::string newDir = homeDir + "\\" + updatesDir; + 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) + { + logger->log("Error: %s can't be made, but doesn't exist!", + newDir.c_str()); + errorMessage = _("Error creating updates directory!"); + state = STATE_ERROR; + } +#else + logger->log("Error: %s/%s can't be made, but doesn't exist!", + homeDir.c_str(), updatesDir.c_str()); + errorMessage = _("Error creating updates directory!"); + state = STATE_ERROR; +#endif + } + } +} + +void Client::initScreenshotDir(const std::string &dir) +{ + if (dir.empty()) + { + screenshotDir = std::string(PHYSFS_getUserDir()) + "Desktop"; + // If ~/Desktop does not exist, we save screenshots in the user's home. + struct stat statbuf; + if (stat(screenshotDir.c_str(), &statbuf)) + screenshotDir = std::string(PHYSFS_getUserDir()); + } + else + screenshotDir = dir; +} + +void Client::accountLogin(LoginData *loginData) +{ + logger->log("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 = ""; + + // TODO This is not the best place to save the config, but at least better + // than the login gui window + if (loginData->remember) + config.setValue("username", loginData->username); + config.setValue("remember", loginData->remember); +} |