summaryrefslogblamecommitdiff
path: root/src/client.cpp
blob: eca63ce4300ed3b0e9694e34d2e34826828fab67 (plain) (tree)










































































                                                                         
                        










                                    
                                

                 

      
                     


















































































































                                                                         

                        
                                



                                            




                                             
                                                                






















                                                                               
                                           

                                                                        
                                                                   

     

                                                            










































































































































































































































































































































                                                                                  
                                                                    





























































































                                                                             
                                                                                          

























































































































































































































































































                                                                               
                                        
 
                             


                                                     
                                                         

                                                 




                                                                     
     

                                                         

      
 
                                      

                                                                           























                                                                           



































                                                                            
                           
 
                                           







































































                                                                              
                                                                  

















                                                                          
                                                                  







































                                                                               
/*
 *  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/mkdir.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>
#include "utils/specialfolder.h"
#else
#include <cerrno>
#endif

#include <sys/stat.h>
#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;

    logger = new Logger;

    // Load branding information
    if (!options.brandingPath.empty())
    {
        branding.init(options.brandingPath);
    }

    initHomeDir(options);
    initScreenshotDir(options.screenshotDir);

    // Configure logger
    logger->setLogFile(localDataDir + 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(localDataDir))
    {
        logger->error(strprintf("%s couldn't be set as home directory! "
                                "Exiting.", localDataDir.c_str()));
    }

    // Add the local data directory to PhysicsFS search path
    resman->addToSearchPath(localDataDir, 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(&currentServer,
                                                         configDir);
                    }
                    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,
                                localDataDir + "/" + 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)
{
    localDataDir = options.localDataDir;

    if (localDataDir.empty())
    {
#ifdef __APPLE__
        // Use Application Directory instead of .mana
        localDataDir = std::string(PHYSFS_getUserDir()) +
            "/Library/Application Support/" +
            branding.getValue("appName", "Mana");
#elif defined WIN32
        localDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA);
        if (localDataDir.empty())
            localDataDir = std::string(PHYSFS_getUserDir());
        localDataDir += "/Mana";
#else
        localDataDir = std::string(PHYSFS_getUserDir()) +
            "/.local/share/mana";
#endif
    }

    if (mkdir_r(localDataDir.c_str()))
    {
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
                                  "Exiting."), localDataDir.c_str()));
    }

    configDir = options.configDir;

    if (configDir.empty()){
#ifdef __APPLE__
        configDir = localDataDir;
#elif defined WIN32
        configDir = getSpecialFolderLocation(CSIDL_APPDATA);
        if (configDir.empty())
            configDir = localDataDir;
        else
            configDir += "/mana/" + branding.getValue("appName", "Mana");
#else
        configDir = std::string(PHYSFS_getUserDir()) +
            "/.config/mana/" + branding.getValue("appShort", "mana");
#endif
    }

    if (mkdir_r(configDir.c_str()))
    {
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
                                  "Exiting."), configDir.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;

    configPath = configDir + "/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 = localDataDir + "\\" + 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!",
                        localDataDir.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);
}