summaryrefslogblamecommitdiff
path: root/src/main.cpp
blob: f540dacdc9346730de16546796f13b35c6996340 (plain) (tree)
1
  


















                                                                             
        
   

                 
 
                   

                   
                   
                 

                      

                                     



                                   
                                                 
                 
                     

                   


                          
                 
                     

                        
                
                      


                           

                  
                            
                           

                      
                          
                         
                             
                          
 




                                  
                         
 

                                      
                                
 
                       
                           
 
                
                 
                  
 
                                 
                        
                   
 
                    
                         
                    
                          
 
            
           
 

                                                                   
 

                                                     
     


                                                                      

                    
 
   

                                                                          
   
                  
 
                                                  
                  
     
                                                         
 
                                                 
                                                                               

                              


                                                                      

                
      
 
 




                            

                                             
                                  
                                  
                                                              
                                 
     
                                 
      





                                       
                                   
                                                                   
                                       
                                        
 

                                                                            
                      

                                                     

                                              
                          
                                                            
                                                  
     

                                                          
                                                    
            


                                
















                                                                               
 
                                              
                                                                          
 
























                                                                         
                 

                                                         

                                                     
 
                                  
                                                                 




                                  





                                                               
                                        

                                                                         


                                                                            
                

     

                             
 
                                        
                                                             
                                                                             
                                        
                                                               
                                                                               
 
 
                                                
     
                                                                      

                                                                        





                                                      
 
                            
                                                           
 
                              
         

                                               
         

                                                                      

                             
                            
                           
                                        
     


                       

                  
                   

                    
 

                                                 
                    


                           


                       
 

                     
 
                                      

 



                                                                            

              







                            


                    


                         
                           



                






                                                                      

                                                                                            
                     



                                                           
                                        

                                          




                                                    
                                                    
             
















                                                                            








                                             


                                            



         








                                                                          
                                                         


                                                   
                                                                         


     
                                    
                    




                                                 
                                       
 
                                                               

                                                 
                                         

                                              

                       
                                


                                         
                                         
 


                                                                    






                                                                              

 
                                          
 
                                                               
                                            
                                         

                                              

                       
                                   



                                         
                                         

 
                                   
 
                                               
 


                                                                          
                                                    


                                                
                                                    

 
           

                                
                      
                                                                    
      
 
                                 
                    
                                      

                          



                    










                                                                        

                                                                        
                                           
                    

                        



                                             

                  
                                                                     
 
                                 
                                  
                      
 
                                                                
 

                                          
                                             
                                                                 


                                    
                                              
     


                                                                  
 



                                                                    
                          
 
 

                    
                               




                                       
                                       
                          

                                 
                                                            
                                           
                          
             

                                       
         
 
                     
                         
 
                                                      
         
                                

                                                           
 








                                                                    

                                                   
                      

                                                  
      


                                 

                                                                
                                                       







                                               

                                                      




                                           
                                

                                                          
             

                              
 


                                                                           
                                                      
             
 

                             

                                                          
                                     
                                     


                            



                                                                             
                                                      















                                                                         
                                                








                                                                
                          
 
                                    
                                                   
                                                                   
                          
 




                                                
                                                      
                                                                    













                                                                                    

                                                                         
                     
                          
 
                                 
                                                


                                                                        


                                                       
                          
 

                                                       
                                                           
                          
 
                                
                                         
                                             
 




                                               
                                    


                                       

                          
                        
                                       

                          
         
     
 
                        
                        
 



                         
                               
                  
                    
                  
             
 
/*
 *  The Mana World
 *  Copyright 2004 The Mana World Development Team
 *
 *  This file is part of The Mana World.
 *
 *  The Mana World 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.
 *
 *  The Mana World 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 The Mana World; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  $Id$
 */

#include "main.h"

#include <getopt.h>
#include <iostream>
#include <physfs.h>
#include <unistd.h>
#include <vector>
#include <SDL_image.h>

#include <guichan/actionlistener.hpp>

#include <guichan/sdl/sdlinput.hpp>

#include <libxml/parser.h>

#if (defined __USE_UNIX98 || defined __FreeBSD__)
#include <cerrno>
#include <sys/stat.h>
#elif defined WIN32
#define NOGDI
#endif

#include "configuration.h"
#include "game.h"
#include "graphics.h"
#include "lockedarray.h"
#include "localplayer.h"
#include "log.h"
#include "logindata.h"
#ifdef USE_OPENGL
#include "openglgraphics.h"
#endif
#include "sound.h"

#include "gui/char_select.h"
#include "gui/connection.h"
#include "gui/gui.h"
#include "gui/login.h"
#include "gui/ok_dialog.h"
#include "gui/register.h"
#include "gui/updatewindow.h"
#include "gui/textfield.h"

#include "net/charserverhandler.h"
#include "net/loginhandler.h"
#include "net/maploginhandler.h"
#include "net/messageout.h"
#include "net/network.h"
#include "net/protocol.h"

#include "resources/image.h"
#include "resources/resourcemanager.h"
#include "resources/spriteset.h"

#include "utils/dtor.h"
#include "utils/tostring.h"

// Account infos
char n_character;
std::string token;

std::vector<Spriteset *> hairset;
Spriteset *playerset[2];
Graphics *graphics;

unsigned char state;
std::string errorMessage;
std::string homeDir;
unsigned char screen_mode;

Sound sound;
Music *bgm;

Configuration config;         /**< Xml file configuration reader */
Logger *logger;               /**< Log object */

namespace {
    struct ErrorListener : public gcn::ActionListener
    {
        void action(const std::string &eventId, gcn::Widget *widget) {
            state = STATE_CHOOSE_SERVER;
        }
    } errorListener;
}

/**
 * Initializes the home directory. On UNIX and FreeBSD, ~/.tmw is used. On
 * Windows and other systems we use the current working directory.
 */
void initHomeDir()
{
#if !(defined __USE_UNIX98 || defined __FreeBSD__)
    homeDir = ".";
#else
    homeDir = std::string(PHYSFS_getUserDir()) + "/.tmw";

    // Checking if /home/user/.tmw folder exists.
    if ((mkdir(homeDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) &&
            (errno != EEXIST))
    {
        std::cout << homeDir
                  << " can't be made, but it doesn't exist! Exitting."
                  << std::endl;
        exit(1);
    }
#endif
}

/**
 * Initialize configuration.
 */
void initConfiguration()
{
    // Fill configuration with defaults
    config.setValue("host", "animesites.de");
    config.setValue("port", 9601);
    config.setValue("hwaccel", 0);
#if (defined __APPLE__ || defined WIN32) && defined USE_OPENGL
    config.setValue("opengl", 1);
#else
    config.setValue("opengl", 0);
#endif
    config.setValue("screen", 0);
    config.setValue("sound", 1);
    config.setValue("guialpha", 0.8f);
    config.setValue("remember", 1);
    config.setValue("sfxVolume", 100);
    config.setValue("musicVolume", 60);
    config.setValue("fpslimit", 0);
    config.setValue("updatehost", "http://themanaworld.org/files");
    config.setValue("customcursor", 1);
    config.setValue("homeDir", homeDir);

    // Checking if the configuration file exists... otherwise create it with
    // default options.
    FILE *tmwFile = 0;
    std::string configPath = homeDir + "/config.xml";
    tmwFile = fopen(configPath.c_str(), "r");

    // If we can't read it, it doesn't exist !
    if (tmwFile == NULL) {
        // We reopen the file in write mode and we create it
        tmwFile = fopen(configPath.c_str(), "wt");
    }
    if (tmwFile == NULL) {
        std::cout << "Can't create " << configPath << ". "
                  << "Using Defaults." << std::endl;
    } else {
        fclose(tmwFile);
        config.init(configPath);
    }
}

/**
 * Do all initialization stuff
 */
void init_engine()
{
    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
        std::cerr << "Could not initialize SDL: " <<
            SDL_GetError() << std::endl;
        exit(1);
    }
    atexit(SDL_Quit);

    SDL_EnableUNICODE(1);
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

    SDL_WM_SetCaption("The Mana World", NULL);
    SDL_WM_SetIcon(IMG_Load(TMW_DATADIR "data/icons/tmw-icon.png"), NULL);

    ResourceManager *resman = ResourceManager::getInstance();

    if (!resman->setWriteDir(homeDir)) {
        std::cout << homeDir
                  << " couldn't be set as home directory! Exitting."
                  << std::endl;
        exit(1);
    }

    // Add the user's homedir to PhysicsFS search path
    resman->addToSearchPath(homeDir, false);
    // Creating and checking the updates folder existence and rights.
    if (!resman->isDirectory("/updates")) {
        if (!resman->mkdir("/updates")) {
            std::cout << homeDir << "/updates "
                      << "can't be made, but it doesn't exist! Exitting."
                      << std::endl;
            exit(1);
        }
    }

    // Add the main data directory to our PhysicsFS search path
    resman->addToSearchPath("data", true);
    resman->addToSearchPath(TMW_DATADIR "data", true);

#ifdef USE_OPENGL
    bool useOpenGL = (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

    int width = (int)config.getValue("screenwidth", 800);
    int height = (int)config.getValue("screenheight", 600);
    int bpp = 0;
    bool fullscreen = ((int)config.getValue("screen", 0) == 1);
    bool hwaccel = ((int)config.getValue("hwaccel", 0) == 1);

    // Try to set the desired video mode
    if (!graphics->setVideoMode(width, height, bpp, fullscreen, hwaccel))
    {
        std::cerr << "Couldn't set "
                  << width << "x" << height << "x" << bpp << " video mode: "
                  << SDL_GetError() << std::endl;
        exit(1);
    }

    // Initialize for drawing
    graphics->_beginDraw();

    playerset[0] = resman->getSpriteset(
            "graphics/sprites/player_male_base.png", 64, 64);
    if (!playerset[0]) logger->error("Couldn't load male player spriteset!");
    playerset[1] = resman->getSpriteset(
            "graphics/sprites/player_female_base.png", 64, 64);
    if (!playerset[1]) logger->error("Couldn't load female player spriteset!");


    for (int i = 0; i < NR_HAIR_STYLES - 1; i++)
    {
        Spriteset *tmp = ResourceManager::getInstance()->getSpriteset(
                "graphics/sprites/hairstyle" + toString(i + 1) + ".png",
                40, 40);
        if (!tmp) {
            logger->error("Unable to load hairstyle");
        } else {
            hairset.push_back(tmp);
        }
    }

    gui = new Gui(graphics);
    state = STATE_CHOOSE_SERVER; /**< Initial game state */

    // Initialize sound engine
    try {
        if (config.getValue("sound", 0) == 1) {
            sound.init();
        }
        sound.setSfxVolume((int)config.getValue("sfxVolume", 100));
        sound.setMusicVolume((int)config.getValue("musicVolume", 60));
    }
    catch (const char *err) {
        state = STATE_ERROR;
        errorMessage = err;
        logger->log("Warning: %s", err);
    }
}

/** Clear the engine */
void exit_engine()
{
    config.write();
    delete gui;
    delete graphics;

    std::for_each(hairset.begin(), hairset.end(),
            std::mem_fun(&Spriteset::decRef));
    hairset.clear();

    playerset[0]->decRef();
    playerset[1]->decRef();

    // Shutdown libxml
    xmlCleanupParser();

    // Shutdown sound
    sound.close();

    ResourceManager::deleteInstance();
}

/**
 * A structure holding the values of various options that can be passed from
 * the command line.
 */
struct Options
{
    /**
     * Constructor.
     */
    Options():
        printHelp(false),
        skipUpdate(false),
        chooseDefault(false)
    {};

    bool printHelp;
    bool skipUpdate;
    bool chooseDefault;
    std::string username;
    std::string password;
    std::string playername;
};

void printHelp()
{
    std::cout
        << "tmw" << std::endl << std::endl
        << "Options: " << std::endl
        << "  -h --help       : Display this help" << std::endl
        << "  -u --skipupdate : Skip the update process" << std::endl
        << "  -U --username   : Login with this username" << std::endl
        << "  -P --password   : Login with this password" << std::endl
        << "  -D --default    : Bypass the login process with default settings" << std::endl
        << "  -p --playername : Login with this player"
        << std::endl;
}

void parseOptions(int argc, char *argv[], Options &options)
{
    const char *optstring = "huU:P:Dp:";

    const struct option long_options[] = {
        { "help",       no_argument,       0, 'h' },
        { "skipupdate", no_argument,       0, 'u' },
        { "username",   required_argument, 0, 'U' },
        { "password",   required_argument, 0, 'P' },
        { "default",    no_argument,       0, 'D' },
        { "playername", required_argument, 0, 'p' },
        { 0 }
    };

    while (optind < argc) {
        int result = getopt_long(argc, argv, optstring, long_options, NULL);

        if (result == -1) {
            break;
        }

        switch (result) {
            default: // Unknown option
            case 'h':
                options.printHelp = true;
                break;
            case 'u':
                options.skipUpdate = true;
                break;
            case 'U':
                options.username = optarg;
                break;
            case 'P':
                options.password = optarg;
                break;
            case 'D':
                options.chooseDefault = true;
                break;
            case 'p':
                options.playername = optarg;
                break;
        }
    }
}

/**
 * Reads the file "updates/resources.txt" and attempts to load each update
 * mentioned in it.
 */
void loadUpdates()
{
    const std::string updatesFile = "updates/resources.txt";
    ResourceManager *resman = ResourceManager::getInstance();
    std::vector<std::string> lines = resman->loadTextFile(updatesFile);
    std::string homeDir = config.getValue("homeDir", "");

    for (unsigned int i = 0; i < lines.size(); ++i)
    {
        resman->addToSearchPath(homeDir + "/updates/" + lines[i], false);
    }
}

CharServerHandler charServerHandler;
LoginData loginData;
LoginHandler loginHandler;
LockedArray<LocalPlayer*> charInfo(MAX_SLOT + 1);
MapLoginHandler mapLoginHandler;

// TODO Find some nice place for these functions
void accountLogin(LoginData *loginData)
{
    logger->log("Username is %s", loginData->username.c_str());
    Network::registerHandler(&loginHandler);
    Network::registerHandler(&charServerHandler);
    loginHandler.setLoginData(loginData);
    charServerHandler.setLoginData(loginData);
    charServerHandler.setCharInfo(&charInfo);

    // Send login infos
    MessageOut msg(PAMSG_LOGIN);
    msg.writeLong(0); // client version
    msg.writeString(loginData->username);
    msg.writeString(loginData->password);
    Network::send(Network::ACCOUNT, msg);

    // 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("host", loginData->hostname);
        config.setValue("username", loginData->username);
    }
    config.setValue("remember", loginData->remember);
}

void accountRegister(LoginData *loginData)
{
    logger->log("Username is %s", loginData->username.c_str());
    Network::registerHandler(&loginHandler);
    loginHandler.setLoginData(loginData);
    charServerHandler.setLoginData(loginData);
    charServerHandler.setCharInfo(&charInfo);

    // Send login infos
    MessageOut msg(PAMSG_REGISTER);
    msg.writeLong(0); // client version
    msg.writeString(loginData->username);
    msg.writeString(loginData->password);
    msg.writeString(loginData->email);
    Network::send(Network::ACCOUNT, msg);
}

void mapLogin(LoginData *loginData)
{
    Network::registerHandler(&mapLoginHandler);

    // Send connect messages with the magic token to game and chat servers
    MessageOut gameServerConnect(PGMSG_CONNECT);
    gameServerConnect.writeString(token, 32);
    Network::send(Network::GAME, gameServerConnect);

    MessageOut chatServerConnect(PCMSG_CONNECT);
    chatServerConnect.writeString(token, 32);
    Network::send(Network::CHAT, chatServerConnect);
}

/** Main */
int main(int argc, char *argv[])
{
#ifdef PACKAGE_VERSION
    std::cout << "The Mana World v" << PACKAGE_VERSION << std::endl;
#endif

    // Parse command line options
    Options options;
    parseOptions(argc, argv, options);
    if (options.printHelp)
    {
        printHelp();
        return 0;
    }

    // Initialize PhysicsFS
    PHYSFS_init(argv[0]);

    initHomeDir();
    initConfiguration();

    // Configure logger
    logger = new Logger();
    logger->setLogFile(homeDir + std::string("/tmw.log"));
    logger->setLogToStandardOut(config.getValue("logToStandardOut", 0));

    // Initialize libxml2 and check for potential ABI mismatches between
    // compiled version and the shared library actually used.
    logger->log("Initializing libxml2...");
    xmlInitParser();
    LIBXML_TEST_VERSION;

    // Redirect libxml errors to /dev/null
    FILE *nullFile = fopen("/dev/null", "w");
    xmlSetGenericErrorFunc(nullFile, NULL);

    init_engine();

    unsigned int oldstate = !state; // We start with a status change.

    Window *currentDialog = NULL;
    Image *login_wallpaper = NULL;
    Game *game = NULL;

    sound.playMusic(TMW_DATADIR "data/music/Magick - Real.ogg");

    loginData.username = options.username;
    if (loginData.username.empty()) {
        if (config.getValue("remember", 0)) {
            loginData.username = config.getValue("username", "");
        }
    }
    if (!options.password.empty()) {
        loginData.password = options.password;
    }
    loginData.hostname = config.getValue("host", "animesites.de");
    loginData.port = (short)config.getValue("port", 0);
    loginData.remember = config.getValue("remember", 0);

    if (enet_initialize() != 0)
    {
        logger->error("An error occurred while initializing ENet.");
    }
    Network::initialize();


    SDL_Event event;

    while (state != STATE_EXIT)
    {
        // Handle SDL events
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_QUIT:
                    state = STATE_EXIT;
                    break;

                case SDL_KEYDOWN:
                    if (event.key.keysym.sym == SDLK_ESCAPE)
                        state = STATE_EXIT;
                    break;
            }

            guiInput->pushInput(event);
        }

        gui->logic();
        Network::flush();

        if (Network::getState() == Network::NET_ERROR)
        {
            state = STATE_ERROR;
            errorMessage = "Got disconnected from server!";
        }

        if (!login_wallpaper)
        {
            login_wallpaper = ResourceManager::getInstance()->
                    getImage("graphics/images/login_wallpaper.png");
            if (!login_wallpaper)
            {
                logger->error("Couldn't load login_wallpaper.png");
            }
        }

        graphics->drawImage(login_wallpaper, 0, 0);
#ifdef PACKAGE_VERSION
        graphics->setFont(gui->getFont());
        graphics->drawText(PACKAGE_VERSION, 0, 0);
#endif
        gui->draw();
        graphics->updateScreen();

        // TODO: Add connect timeout to go back to choose server
        if (state == STATE_CONNECT_ACCOUNT &&
                Network::isConnected(Network::ACCOUNT))
        {
            if (options.skipUpdate) {
                state = STATE_LOGIN;
            } else {
                state = STATE_UPDATE;
            }
        }
        else if (state == STATE_CONNECT_GAME &&
                Network::isConnected(Network::GAME) &&
                Network::isConnected(Network::CHAT))
        {
            // TODO: Somehow send the token
            state = STATE_GAME;
        }

        if (state != oldstate) {
            // Load updates after exiting the update state
            if (oldstate == STATE_UPDATE)
            {
                loadUpdates();
            }

            // Disconnect from account server once connected to game server
            if (oldstate == STATE_CONNECT_GAME && state == STATE_GAME)
            {
                Network::disconnect(Network::ACCOUNT);
            }

            oldstate = state;

            // Get rid of the dialog of the previous state
            if (currentDialog) {
                delete currentDialog;
                currentDialog = NULL;
            }

            switch (state) {
                case STATE_CHOOSE_SERVER:
                    logger->log("State: CHOOSE_SERVER");
                    // TODO: Allow changing this using a server choice dialog
                    logger->log("Trying to connect to account server...");
                    Network::connect(Network::ACCOUNT,
                                     loginData.hostname, loginData.port);
                    state = STATE_CONNECT_ACCOUNT;
                    break;

                case STATE_CONNECT_ACCOUNT:
                    logger->log("State: CONNECT_ACCOUNT");
                    break;

                case STATE_UPDATE:
                    logger->log("State: UPDATE");
                    // TODO: Revive later
                    //currentDialog = new UpdaterWindow();
                    state = STATE_LOGIN;
                    break;

                case STATE_LOGIN:
                    logger->log("State: LOGIN");
                    currentDialog = new LoginDialog(&loginData);
                    // TODO: Restore autologin
                    //if (!loginData.password.empty()) {
                    //    accountLogin(&loginData);
                    //}
                    break;

                case STATE_LOGIN_ATTEMPT:
                    accountLogin(&loginData);
                    break;

                case STATE_REGISTER:
                    logger->log("State: REGISTER");
                    currentDialog = new RegisterDialog(&loginData);
                    break;

                case STATE_REGISTER_ATTEMPT:
                    accountRegister(&loginData);
                    break;

                case STATE_CHAR_SELECT:
                    logger->log("State: CHAR_SELECT");
                    currentDialog = new CharSelectDialog(&charInfo);
                    if (options.playername != "") {
                        n_character = 0;
                        while (((CharSelectDialog*) currentDialog)->getName()
                                != options.playername &&
                                n_character < MAX_SLOT + 1)
                        {
                            ((CharSelectDialog*) currentDialog)->action("next",
                                                                        NULL);
                            ((CharSelectDialog*) currentDialog)->updatePlayerInfo();
                            n_character++;
                        }
                        n_character = MAX_SLOT + 1;
                    }
                    if (options.chooseDefault || options.playername != "") {
                        ((CharSelectDialog*)currentDialog)->action("ok",
                                                                   NULL);
                    }
                    break;

                case STATE_ERROR:
                    logger->log("State: ERROR");
                    currentDialog = new OkDialog("Error", errorMessage);
                    currentDialog->addActionListener(&errorListener);
                    currentDialog = NULL; // OkDialog deletes itself
                    Network::disconnect(Network::GAME);
                    Network::disconnect(Network::CHAT);
                    Network::clearHandlers();
                    break;

                case STATE_CONNECT_GAME:
                    logger->log("State: CONNECT_GAME");
                    currentDialog = new ConnectionDialog();
                    break;

                case STATE_GAME:
                    mapLogin(&loginData);
                    sound.fadeOutMusic(1000);

                    currentDialog = NULL;
                    login_wallpaper->decRef();
                    login_wallpaper = NULL;

                    logger->log("State: GAME");
                    game = new Game;
                    game->logic();
                    delete game;
                    state = STATE_EXIT;
                    break;

                default:
                    state = STATE_EXIT;
                    break;
            }
        }
    }

    Network::finalize();
    enet_deinitialize();

    if (nullFile)
    {
        fclose(nullFile);
    }
    logger->log("State: EXIT");
    exit_engine();
    PHYSFS_deinit();
    delete logger;
    return 0;
}