1
/*
2
 *  The Mana World
3
 *  Copyright (C) 2004  The Mana World Development Team
4
 *
5
 *  This file is part of The Mana World.
6
 *
7
 *  This program is free software; you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
9
 *  the Free Software Foundation; either version 2 of the License, or
10
 *  any later version.
11
 *
12
 *  This program is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU General Public License
18
 *  along with this program; if not, write to the Free Software
19
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
 */
21
22
#include "main.h"
23
24
#include "configuration.h"
25
#include "emoteshortcut.h"
26
#include "game.h"
27
#include "graphics.h"
28
#include "itemshortcut.h"
29
#include "keyboardconfig.h"
30
#include "localplayer.h"
31
#include "lockedarray.h"
32
#include "log.h"
33
#ifdef USE_OPENGL
34
#include "openglgraphics.h"
35
#endif
36
#include "playerrelations.h"
37
#include "sound.h"
38
#include "statuseffect.h"
39
#include "units.h"
40
41
#include "gui/widgets/button.h"
42
#include "gui/widgets/desktop.h"
43
#include "gui/widgets/label.h"
44
#include "gui/widgets/progressbar.h"
45
46
#include "gui/changeemaildialog.h"
47
#include "gui/changepassworddialog.h"
48
#include "gui/charselectdialog.h"
49
#include "gui/connectiondialog.h"
50
#include "gui/gui.h"
51
#include "gui/skin.h"
52
#include "gui/login.h"
53
#include "gui/okdialog.h"
54
#include "gui/palette.h"
55
#include "gui/quitdialog.h"
56
#include "gui/register.h"
57
#include "gui/sdlinput.h"
58
#include "gui/serverdialog.h"
59
#include "gui/setup.h"
60
#include "gui/unregisterdialog.h"
61
#include "gui/updatewindow.h"
62
#include "gui/worldselectdialog.h"
63
64
#include "net/charhandler.h"
65
#include "net/gamehandler.h"
66
#include "net/generalhandler.h"
67
#include "net/logindata.h"
68
#include "net/loginhandler.h"
69
#include "net/net.h"
70
#include "net/worldinfo.h"
71
#ifdef TMWSERV_SUPPORT
72
#include "net/tmwserv/charhandler.h"
73
#include "net/tmwserv/connection.h"
74
#include "net/tmwserv/generalhandler.h"
75
#include "net/tmwserv/loginhandler.h"
76
#include "net/tmwserv/network.h"
77
#endif
78
79
#ifdef TMWSERV_SUPPORT
80
#include "net/tmwserv/accountserver/accountserver.h"
81
#include "net/tmwserv/accountserver/account.h"
82
83
#include "net/tmwserv/chatserver/chatserver.h"
84
85
#include "net/tmwserv/gameserver/gameserver.h"
86
#endif
87
88
#include "resources/colordb.h"
89
#include "resources/emotedb.h"
90
#include "resources/image.h"
91
#include "resources/itemdb.h"
92
#include "resources/monsterdb.h"
93
#include "resources/npcdb.h"
94
#include "resources/resourcemanager.h"
95
96
#include "utils/gettext.h"
97
#include "utils/stringutils.h"
98
99
#include <SDL_image.h>
100
101
#include <guichan/actionlistener.hpp>
102
103
#include <libxml/parser.h>
104
105
#include <getopt.h>
106
#include <iostream>
107
#include <physfs.h>
108
#include <unistd.h>
109
#include <vector>
110
111
#ifdef __APPLE__
112
#include <CoreFoundation/CFBundle.h>
113
#endif
114
115
#ifdef __MINGW32__
116
#include <windows.h>
117
#define usleep(usec) (Sleep ((usec) / 1000), 0)
118
#endif
119
120
#ifdef WIN32
121
#include <SDL_syswm.h>
122
#else
123
#include <cerrno>
124
#include <sys/stat.h>
125
#endif
126
127
namespace
128
{
129
    class SetupListener : public gcn::ActionListener
130
    {
131
    public:
132
        /**
133
         * Called when receiving actions from widget.
134
         */
135
        void action(const gcn::ActionEvent &event);
136
    } listener;
137
}
138
139
static const int defaultSfxVolume = 100;
140
static const int defaultMusicVolume = 60;
141
142
#ifdef TMWSERV_SUPPORT
143
extern Net::Connection *gameServerConnection;
144
extern Net::Connection *chatServerConnection;
145
extern Net::Connection *accountServerConnection;
146
#endif
147
148
Graphics *graphics;
149
Game *game = 0;
150
151
State state = STATE_START;
152
std::string errorMessage;
153
154
Sound sound;
155
Music *bgm;
156
157
Configuration config;         /**< XML file configuration reader */
158
Configuration branding;       /**< XML branding information reader */
159
Logger *logger;               /**< Log object */
160
KeyboardConfig keyboard;
161
162
LoginData loginData;
163
LockedArray<LocalPlayer*> charInfo(MAX_CHARACTER_COUNT);
164
165
Palette *guiPalette;
166
167
// This anonymous namespace hides whatever is inside from other modules.
168
namespace {
169
170
std::string homeDir;
171
std::string updateHost;
172
std::string updatesDir;
173
174
SDL_Surface *icon;
175
176
/**
177
 * A structure holding the values of various options that can be passed from
178
 * the command line.
179
 */
180
struct Options
181
{
182
    /**
183
     * Constructor.
184
     */
185
    Options():
186
        printHelp(false),
187
        printVersion(false),
188
        skipUpdate(false),
189
        skipUpdateLoad(false),
190
        chooseDefault(false),
191
        noOpenGL(false),
192
        serverPort(0)
193
    {}
194
195
    bool printHelp;
196
    bool printVersion;
197
    bool skipUpdate;
198
    bool skipUpdateLoad;
199
    bool chooseDefault;
200
    bool noOpenGL;
201
    std::string username;
202
    std::string password;
203
    std::string character;
204
    std::string configPath;
205
    std::string updateHost;
206
    std::string dataPath;
207
    std::string homeDir;
208
209
    std::string serverName;
210
    short serverPort;
211
212
};
213
214
/**
215
 * Parse the update host and determine the updates directory
216
 * Then verify that the directory exists (creating if needed).
217
 */
218
static void setUpdatesDir()
219
{
220
    std::stringstream updates;
221
222
    // If updatesHost is currently empty, fill it from config file
223
    if (updateHost.empty())
224
    {
225
        updateHost =
226
            config.getValue("updatehost", "http://updates.themanaworld.org/");
227
    }
228
229
    // Remove any trailing slash at the end of the update host
230
    if (updateHost.at(updateHost.size() - 1) == '/')
231
        updateHost.resize(updateHost.size() - 1);
232
233
    // Parse out any "http://" or "ftp://", and set the updates directory
234
    size_t pos;
235
    pos = updateHost.find("://");
236
    if (pos != updateHost.npos)
237
    {
238
        if (pos + 3 < updateHost.length())
239
        {
240
            updates << "updates/" << updateHost.substr(pos + 3);
241
            updatesDir = updates.str();
242
        }
243
        else
244
        {
245
            logger->log("Error: Invalid update host: %s", updateHost.c_str());
246
            errorMessage = strprintf(_("Invalid update host: %s"), updateHost.c_str());
247
            state = STATE_ERROR;
248
        }
249
    }
250
    else
251
    {
252
        logger->log("Warning: no protocol was specified for the update host");
253
        updates << "updates/" << updateHost;
254
        updatesDir = updates.str();
255
    }
256
257
    ResourceManager *resman = ResourceManager::getInstance();
258
259
    // Verify that the updates directory exists. Create if necessary.
260
    if (!resman->isDirectory("/" + updatesDir))
261
    {
262
        if (!resman->mkdir("/" + updatesDir))
263
        {
264
#if defined WIN32
265
            std::string newDir = homeDir + "\\" + updatesDir;
266
            std::string::size_type loc = newDir.find("/", 0);
267
268
            while (loc != std::string::npos)
269
            {
270
                newDir.replace(loc, 1, "\\");
271
                loc = newDir.find("/", loc);
272
            }
273
274
            if (!CreateDirectory(newDir.c_str(), 0) &&
275
                GetLastError() != ERROR_ALREADY_EXISTS)
276
            {
277
                logger->log("Error: %s can't be made, but doesn't exist!",
278
                            newDir.c_str());
279
                errorMessage = _("Error creating updates directory!");
280
                state = STATE_ERROR;
281
            }
282
#else
283
            logger->log("Error: %s/%s can't be made, but doesn't exist!",
284
                        homeDir.c_str(), updatesDir.c_str());
285
            errorMessage = _("Error creating updates directory!");
286
            state = STATE_ERROR;
287
#endif
288
        }
289
    }
290
}
291
292
/**
293
 * Initializes the home directory. On UNIX and FreeBSD, ~/.tmw is used. On
294
 * Windows and other systems we use the current working directory.
295
 */
296
static void initHomeDir(const Options &options)
297
{
298
    homeDir = options.homeDir;
299
300
    if (homeDir.empty())
301
    {
302
#ifdef __APPLE__
303
        // Use Application Directory instead of .tmw
304
        homeDir = std::string(PHYSFS_getUserDir()) +
305
            "/Library/Application Support/" +
306
            branding.getValue("appName", "The Mana World");
307
#else
308
        homeDir = std::string(PHYSFS_getUserDir()) +
309
            "/." + branding.getValue("appShort", "tmw");
310
#endif
311
    }
312
#if defined WIN32
313
    if (!CreateDirectory(homeDir.c_str(), 0) &&
314
            GetLastError() != ERROR_ALREADY_EXISTS)
315
#else
316
    // Create home directory if it doesn't exist already
317
    if ((mkdir(homeDir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) &&
318
            (errno != EEXIST))
319
#endif
320
    {
321
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
322
                                  "Exiting."), homeDir.c_str()));
323
    }
324
}
325
326
/**
327
 * Initialize configuration.
328
 */
329
static void initConfiguration(const Options &options)
330
{
331
    // Fill configuration with defaults
332
    logger->log("Initializing configuration...");
333
    std::string defaultHost = branding.getValue("defaultServer",
334
        "server.themanaworld.org");
335
    int defaultPort = (int)branding.getValue("defaultPort", DEFAULT_PORT);
336
    config.setValue("port", defaultPort);
337
    config.setValue("hwaccel", false);
338
#if (defined __APPLE__ || defined WIN32) && defined USE_OPENGL
339
    config.setValue("opengl", true);
340
#else
341
    config.setValue("opengl", false);
342
#endif
343
    config.setValue("screen", false);
344
    config.setValue("sound", true);
345
    config.setValue("guialpha", 0.8f);
346
    config.setValue("remember", true);
347
    config.setValue("sfxVolume", 100);
348
    config.setValue("musicVolume", 60);
349
    config.setValue("fpslimit", 60);
350
    std::string defaultUpdateHost = branding.getValue("defaultUpdateHost",
351
        "http://updates.themanaworld.org");
352
    config.setValue("updatehost", defaultUpdateHost);
353
    config.setValue("customcursor", true);
354
    config.setValue("ChatLogLength", 128);
355
356
    // Checking if the configuration file exists... otherwise create it with
357
    // default options.
358
    FILE *configFile = 0;
359
    std::string configPath = options.configPath;
360
361
    if (configPath.empty())
362
        configPath = homeDir + "/config.xml";
363
364
    configFile = fopen(configPath.c_str(), "r");
365
366
    // If we can't read it, it doesn't exist !
367
    if (configFile == NULL) {
368
        // We reopen the file in write mode and we create it
369
        configFile = fopen(configPath.c_str(), "wt");
370
    }
371
    if (configFile == NULL) {
372
       logger->log("Can't create %s. Using defaults.", configPath.c_str());
373
    } else {
374
        fclose(configFile);
375
        config.init(configPath);
376
    }
377
}
378
379
/**
380
 * Do all initialization stuff.
381
 */
382
static void initEngine(const Options &options)
383
{
384
    // Initialize SDL
385
    logger->log("Initializing SDL...");
386
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) {
387
        logger->error(strprintf("Could not initialize SDL: %s",
388
                      SDL_GetError()));
389
    }
390
    atexit(SDL_Quit);
391
392
    SDL_EnableUNICODE(1);
393
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
394
395
    SDL_WM_SetCaption(branding.getValue("appName", "The Mana World").c_str(),
396
                      NULL);
397
398
    ResourceManager *resman = ResourceManager::getInstance();
399
400
    if (!resman->setWriteDir(homeDir)) {
401
        logger->error(strprintf("%s couldn't be set as home directory! "
402
                                "Exiting.", homeDir.c_str()));
403
    }
404
405
    // Add the user's homedir to PhysicsFS search path
406
    resman->addToSearchPath(homeDir, false);
407
408
    // Add the main data directories to our PhysicsFS search path
409
    if (!options.dataPath.empty()) {
410
        resman->addToSearchPath(options.dataPath, true);
411
    }
412
    resman->addToSearchPath("data", true);
413
#if defined __APPLE__
414
    CFBundleRef mainBundle = CFBundleGetMainBundle();
415
    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
416
    char path[PATH_MAX];
417
    if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path,
418
                                          PATH_MAX))
419
    {
420
        fprintf(stderr, "Can't find Resources directory\n");
421
    }
422
    CFRelease(resourcesURL);
423
    strncat(path, "/data", PATH_MAX - 1);
424
    resman->addToSearchPath(path, true);
425
#else
426
    resman->addToSearchPath(PKG_DATADIR "data", true);
427
#endif
428
429
#ifdef WIN32
430
    static SDL_SysWMinfo pInfo;
431
    SDL_GetWMInfo(&pInfo);
432
    HICON icon = LoadIcon(GetModuleHandle(NULL), "A");
433
    if (icon)
434
    {
435
        SetClassLong(pInfo.window, GCL_HICON, (LONG) icon);
436
    }
437
#else
438
    icon = IMG_Load(resman->getPath(branding.getValue("appIcon", "data/icons/tmw.png")).c_str());
439
    if (icon)
440
    {
441
        SDL_SetAlpha(icon, SDL_SRCALPHA, SDL_ALPHA_OPAQUE);
442
        SDL_WM_SetIcon(icon, NULL);
443
    }
444
#endif
445
446
#ifdef USE_OPENGL
447
    bool useOpenGL = !options.noOpenGL && (config.getValue("opengl", 0) == 1);
448
449
    // Setup image loading for the right image format
450
    Image::setLoadAsOpenGL(useOpenGL);
451
452
    // Create the graphics context
453
    graphics = useOpenGL ? new OpenGLGraphics : new Graphics;
454
#else
455
    // Create the graphics context
456
    graphics = new Graphics;
457
#endif
458
459
    const int width = (int) config.getValue("screenwidth", defaultScreenWidth);
460
    const int height = (int) config.getValue("screenheight", defaultScreenHeight);
461
    const int bpp = 0;
462
    const bool fullscreen = ((int) config.getValue("screen", 0) == 1);
463
    const bool hwaccel = ((int) config.getValue("hwaccel", 0) == 1);
464
465
    // Try to set the desired video mode
466
    if (!graphics->setVideoMode(width, height, bpp, fullscreen, hwaccel))
467
    {
468
        logger->error(strprintf("Couldn't set %dx%dx%d video mode: %s",
469
            width, height, bpp, SDL_GetError()));
470
    }
471
472
    // Initialize for drawing
473
    graphics->_beginDraw();
474
475
    // Initialize the item shortcuts.
476
    itemShortcut = new ItemShortcut;
477
478
    // Initialize the emote shortcuts.
479
    emoteShortcut = new EmoteShortcut;
480
481
    gui = new Gui(graphics);
482
483
    // Initialize sound engine
484
    try
485
    {
486
        if (config.getValue("sound", 0) == 1)
487
            sound.init();
488
489
        sound.setSfxVolume((int) config.getValue("sfxVolume",
490
                    defaultSfxVolume));
491
        sound.setMusicVolume((int) config.getValue("musicVolume",
492
                    defaultMusicVolume));
493
    }
494
    catch (const char *err)
495
    {
496
        state = STATE_ERROR;
497
        errorMessage = err;
498
        logger->log("Warning: %s", err);
499
    }
500
501
    // Initialize keyboard
502
    keyboard.init();
503
504
    // Initialise player relations
505
    player_relations.init();
506
}
507
508
/** Clear the engine */
509
static void exitEngine()
510
{
511
    // Before config.write() since it writes the shortcuts to the config
512
    delete itemShortcut;
513
    delete emoteShortcut;
514
515
    config.write();
516
517
    delete gui;
518
    delete graphics;
519
520
    // Shutdown libxml
521
    xmlCleanupParser();
522
523
    // Shutdown sound
524
    sound.close();
525
526
    // Unload XML databases
527
    ColorDB::unload();
528
    EmoteDB::unload();
529
    ItemDB::unload();
530
    MonsterDB::unload();
531
    NPCDB::unload();
532
    StatusEffect::unload();
533
534
    ResourceManager::deleteInstance();
535
536
    SDL_FreeSurface(icon);
537
}
538
539
static void printHelp()
540
{
541
    using std::endl;
542
543
    std::cout
544
        << _("tmw") << endl << endl
545
        << _("Options:") << endl
546
        << _("  -C --config-file : Configuration file to use") << endl
547
        << _("  -d --data        : Directory to load game data from") << endl
548
        << _("  -D --default     : Choose default character server and "
549
                                  "character") << endl
550
        << _("  -h --help        : Display this help") << endl
551
        << _("  -S --home-dir    : Directory to use as home directory") << endl
552
        << _("  -H --update-host : Use this update host") << endl
553
        << _("  -P --password    : Login with this password") << endl
554
        << _("  -c --character   : Login with this character") << endl
555
        << _("  -p --port        : Login server port") << endl
556
        << _("  -s --server      : Login server name or IP") << endl
557
        << _("  -u --skip-update : Skip the update downloads") << endl
558
        << _("  -l --skip-load   : Skip loading the updates") << endl
559
        << _("  -U --username    : Login with this username") << endl
560
#ifdef USE_OPENGL
561
        << _("  -O --no-opengl   : Disable OpenGL for this session") << endl
562
#endif
563
        << _("  -v --version     : Display the version") << endl;
564
}
565
566
static void printVersion()
567
{
568
    std::cout << strprintf("The Mana World %s", FULL_VERSION) << std::endl;
569
}
570
571
static void parseOptions(int argc, char *argv[], Options &options)
572
{
573
    const char *optstring = "hvuld:U:P:Dc:s:p:C:H:S:O";
574
575
    const struct option long_options[] = {
576
        { "config-file", required_argument, 0, 'C' },
577
        { "data",        required_argument, 0, 'd' },
578
        { "default",     no_argument,       0, 'D' },
579
        { "password",    required_argument, 0, 'P' },
580
        { "character",   required_argument, 0, 'c' },
581
        { "help",        no_argument,       0, 'h' },
582
        { "home-dir",    required_argument, 0, 'S' },
583
        { "update-host", required_argument, 0, 'H' },
584
        { "port",        required_argument, 0, 'p' },
585
        { "server",      required_argument, 0, 's' },
586
        { "skip-update", no_argument,       0, 'u' },
587
        { "skip-load",   no_argument,       0, 'l' },
588
        { "username",    required_argument, 0, 'U' },
589
        { "no-opengl",   no_argument,       0, 'O' },
590
        { "version",     no_argument,       0, 'v' },
591
        { 0 }
592
    };
593
594
    while (optind < argc)
595
    {
596
597
        int result = getopt_long(argc, argv, optstring, long_options, NULL);
598
599
        if (result == -1)
600
            break;
601
602
        switch (result)
603
        {
604
            case 'C':
605
                options.configPath = optarg;
606
                break;
607
            case 'd':
608
                options.dataPath = optarg;
609
                break;
610
            case 'D':
611
                options.chooseDefault = true;
612
                break;
613
            default: // Unknown option
614
            case 'h':
615
                options.printHelp = true;
616
                break;
617
            case 'H':
618
                options.updateHost = optarg;
619
                break;
620
            case 'c':
621
                options.character = optarg;
622
                break;
623
            case 'P':
624
                options.password = optarg;
625
                break;
626
            case 's':
627
                options.serverName = optarg;
628
                break;
629
            case 'p':
630
                options.serverPort = (short) atoi(optarg);
631
                break;
632
            case 'u':
633
                options.skipUpdate = true;
634
                break;
635
            case 'l':
636
                options.skipUpdateLoad = true;
637
                break;
638
            case 'U':
639
                options.username = optarg;
640
                break;
641
            case 'v':
642
                options.printVersion = true;
643
                break;
644
            case 'S':
645
                options.homeDir = optarg;
646
                break;
647
            case 'O':
648
                options.noOpenGL = true;
649
                break;
650
        }
651
    }
652
}
653
654
/**
655
 * Reads the file "{Updates Directory}/resources2.txt" and attempts to load
656
 * each update mentioned in it.
657
 */
658
static void loadUpdates()
659
{
660
    if (updatesDir.empty()) return;
661
    const std::string updatesFile = "/" + updatesDir + "/resources2.txt";
662
    ResourceManager *resman = ResourceManager::getInstance();
663
    std::vector<std::string> lines = resman->loadTextFile(updatesFile);
664
665
    for (unsigned int i = 0; i < lines.size(); ++i)
666
    {
667
        std::stringstream line(lines[i]);
668
        std::string filename;
669
        line >> filename;
670
        resman->addToSearchPath(homeDir + "/" + updatesDir + "/"
671
                                + filename, false);
672
    }
673
}
674
675
class ErrorListener : public gcn::ActionListener
676
{
677
public:
678
    void action(const gcn::ActionEvent &event)
679
    {
680
        state = STATE_CHOOSE_SERVER;
681
    }
682
} errorListener;
683
684
class AccountListener : public gcn::ActionListener
685
{
686
public:
687
    void action(const gcn::ActionEvent &event)
688
    {
689
        state = STATE_CHAR_SELECT;
690
    }
691
} accountListener;
692
693
class LoginListener : public gcn::ActionListener
694
{
695
public:
696
    void action(const gcn::ActionEvent &event)
697
    {
698
        state = STATE_LOGIN;
699
    }
700
} loginListener;
701
702
} // namespace
703
704
// TODO Find some nice place for these functions
705
static void accountLogin(LoginData *loginData)
706
{
707
    logger->log("Username is %s", loginData->username.c_str());
708
709
    Net::getCharHandler()->setCharInfo(&charInfo);
710
711
    // Send login infos
712
    if (loginData->registerLogin) {
713
        Net::getLoginHandler()->registerAccount(loginData);
714
    } else {
715
        Net::getLoginHandler()->loginAccount(loginData);
716
    }
717
718
    // Clear the password, avoids auto login when returning to login
719
    loginData->password = "";
720
721
    // TODO This is not the best place to save the config, but at least better
722
    // than the login gui window
723
    if (loginData->remember)
724
    {
725
        config.setValue("username", loginData->username);
726
    }
727
    config.setValue("remember", loginData->remember);
728
}
729
730
extern "C" char const *_nl_locale_name_default(void);
731
732
static void initInternationalization()
733
{
734
#if ENABLE_NLS
735
#ifdef WIN32
736
    putenv(("LANG=" + std::string(_nl_locale_name_default())).c_str());
737
    // mingw doesn't like LOCALEDIR to be defined for some reason
738
    bindtextdomain("tmw", "translations/");
739
#else
740
    bindtextdomain("tmw", LOCALEDIR);
741
#endif
742
    setlocale(LC_MESSAGES, "");
743
    bind_textdomain_codeset("tmw", "UTF-8");
744
    textdomain("tmw");
745
#endif
746
}
747
748
static void xmlNullLogger(void *ctx, const char *msg, ...)
749
{
750
    // Does nothing, that's the whole point of it
751
}
752
753
// Initialize libxml2 and check for potential ABI mismatches between
754
// compiled version and the shared library actually used.
755
static void initXML()
756
{
757
    xmlInitParser();
758
    LIBXML_TEST_VERSION;
759
760
    // Suppress libxml2 error messages
761
    xmlSetGenericErrorFunc(NULL, xmlNullLogger);
762
}
763
764
/** Main */
765
int main(int argc, char *argv[])
766
{
767
    // Parse command line options
768
    Options options;
769
    parseOptions(argc, argv, options);
770
    if (options.printHelp)
771
    {
772
        printHelp();
773
        return 0;
774
    }
775
    else if (options.printVersion)
776
    {
777
        printVersion();
778
        return 0;
779
    }
780
781
    initInternationalization();
782
783
    // Initialize PhysicsFS
784
    PHYSFS_init(argv[0]);
785
786
    initXML();
787
788
    // Load branding information
789
    branding.init("data/branding.xml");
790
791
    initHomeDir(options);
792
793
    // Configure logger
794
    logger = new Logger;
795
    logger->setLogFile(homeDir + std::string("/tmw.log"));
796
797
    // Log the tmw version
798
    logger->log("The Mana World %s", FULL_VERSION);
799
800
    initConfiguration(options);
801
    logger->setLogToStandardOut(config.getValue("logToStandardOut", 0));
802
803
    initEngine(options);
804
805
    // Needs to be created in main, as the updater uses it
806
    guiPalette = new Palette;
807
808
    Window *currentDialog = NULL;
809
    QuitDialog* quitDialog = NULL;
810
    setupWindow = new Setup;
811
812
    gcn::Container *top = static_cast<gcn::Container*>(gui->getTop());
813
    Desktop *desktop = new Desktop;
814
    top->add(desktop);
815
    ProgressBar *progressBar = new ProgressBar(0.0f, 100, 20,
816
                                               gcn::Color(168, 116, 31));
817
    progressBar->setSmoothProgress(false);
818
    gcn::Label *progressLabel = new Label;
819
    top->add(progressBar, 5, top->getHeight() - 5 - progressBar->getHeight());
820
    top->add(progressLabel, 15 + progressBar->getWidth(),
821
                            progressBar->getY() + 4);
822
    progressBar->setVisible(false);
823
    gcn::Button *setupButton = new Button(_("Setup"), "Setup", &listener);
824
    setupButton->setPosition(top->getWidth() - setupButton->getWidth() - 3, 3);
825
    top->add(setupButton);
826
827
    sound.playMusic(branding.getValue("loginMusic", "Magick - Real.ogg"));
828
829
    // Initialize default server
830
    ServerInfo currentServer;
831
    currentServer.hostname = options.serverName;
832
    currentServer.port = options.serverPort;
833
    loginData.username = options.username;
834
    loginData.password = options.password;
835
    loginData.remember = config.getValue("remember", 0);
836
    loginData.registerLogin = false;
837
838
    if (currentServer.hostname.empty()) {
839
        currentServer.hostname = branding.getValue("defaultServer",
840
                                            "server.themanaworld.org").c_str();
841
    }
842
    if (options.serverPort == 0) {
843
        currentServer.port = (short) branding.getValue("defaultPort",
844
                                                          DEFAULT_PORT);
845
    }
846
    if (loginData.username.empty() && loginData.remember) {
847
        loginData.username = config.getValue("username", "");
848
    }
849
850
    int screenWidth = (int) config.getValue("screenwidth", defaultScreenWidth);
851
    int screenHeight = static_cast<int>(config.getValue("screenheight",
852
                                                        defaultScreenHeight));
853
854
    desktop->setSize(screenWidth, screenHeight);
855
856
    if (state != STATE_ERROR)
857
        state = STATE_CHOOSE_SERVER;
858
    State oldstate = STATE_START; // We start with a status change
859
860
    SDL_Event event;
861
862
    while (state != STATE_EXIT)
863
    {
864
        bool handledEvents = false;
865
866
        // Handle SDL events
867
        while (SDL_PollEvent(&event))
868
        {
869
            handledEvents = true;
870
871
            switch (event.type)
872
            {
873
                case SDL_QUIT:
874
                    state = STATE_EXIT;
875
                    break;
876
877
                case SDL_KEYDOWN:
878
                    if (event.key.keysym.sym == SDLK_ESCAPE)
879
                    {
880
                        if (!quitDialog)
881
                            quitDialog = new QuitDialog(NULL, &quitDialog);
882
                        else
883
                            quitDialog->requestMoveToTop();
884
                    }
885
                    break;
886
            }
887
888
            guiInput->pushInput(event);
889
        }
890
891
        if (Net::getGeneralHandler())
892
        {
893
            Net::getGeneralHandler()->flushNetwork();
894
            Net::getGeneralHandler()->tick();
895
        }
896
        gui->logic();
897
898
        if (progressBar && progressBar->isVisible())
899
        {
900
            progressBar->setProgress(progressBar->getProgress() + 0.005f);
901
            if (progressBar->getProgress() == 1.0f)
902
                progressBar->setProgress(0.0f);
903
        }
904
905
        gui->draw();
906
        graphics->updateScreen();
907
908
        // TODO: Add connect timeouts
909
        if (state == STATE_CONNECT_GAME &&
910
                 Net::getGameHandler()->isConnected())
911
        {
912
            Net::getLoginHandler()->disconnect();
913
914
            state = STATE_GAME;
915
        }
916
        else if (state == STATE_CONNECT_SERVER && oldstate == STATE_CHOOSE_SERVER)
917
        {
918
            Net::connectToServer(currentServer);
919
        }
920
        else if (state == STATE_CONNECT_SERVER &&
921
                 oldstate != STATE_CHOOSE_SERVER &&
922
                 Net::getLoginHandler()->isConnected())
923
        {
924
            Net::getCharHandler()->setCharInfo(&charInfo);
925
            state = STATE_LOGIN;
926
        }
927
928
        if (state != oldstate)
929
        {
930
            //printf("State change: %d to %d\n", oldstate, state);
931
932
            oldstate = state;
933
934
            // Get rid of the dialog of the previous state
935
            if (currentDialog) {
936
                delete currentDialog;
937
                currentDialog = NULL;
938
            }
939
            // State has changed, while the quitDialog was active, it might
940
            // not be correct anymore
941
            if (quitDialog) {
942
                quitDialog->scheduleDelete();
943
            }
944
945
            switch (state) {
946
                case STATE_CHOOSE_SERVER:
947
                    logger->log("State: CHOOSE SERVER");
948
949
                    // Allow changing this using a server choice dialog
950
                    // We show the dialog box only if the command-line
951
                    // options weren't set.
952
                    if (options.serverName.empty() && options.serverPort == 0) {
953
                        // Don't allow an alpha opacity
954
                        // lower than the default value
955
                        SkinLoader::instance()->setMinimumOpacity(0.8f);
956
957
                        currentDialog = new ServerDialog(&currentServer,
958
                                                         homeDir);
959
                    } else {
960
                        state = STATE_CONNECT_SERVER;
961
962
                        // Reset options so that cancelling or connect
963
                        // timeout will show the server dialog.
964
                        options.serverName.clear();
965
                        options.serverPort = 0;
966
                    }
967
                    break;
968
969
                case STATE_CONNECT_SERVER:
970
                    logger->log("State: CONNECT SERVER");
971
                    currentDialog = new ConnectionDialog(STATE_SWITCH_SERVER);
972
                    break;
973
974
                case STATE_LOGIN:
975
                    logger->log("State: LOGIN");
976
                    // Don't allow an alpha opacity
977
                    // lower than the default value
978
                    SkinLoader::instance()->setMinimumOpacity(0.8f);
979
980
                    if (options.username.empty()
981
                            || options.password.empty()) {
982
                        currentDialog = new LoginDialog(&loginData);
983
                    } else {
984
                        state = STATE_LOGIN_ATTEMPT;
985
                        // Clear the password so that when login fails, the
986
                        // dialog will show up next time.
987
                        options.password.clear();
988
                    }
989
                    break;
990
991
                case STATE_LOGIN_ATTEMPT:
992
                    logger->log("State: LOGIN ATTEMPT");
993
                    accountLogin(&loginData);
994
                    break;
995
996
                case STATE_WORLD_SELECT:
997
                    logger->log("State: WORLD SELECT");
998
                    {
999
                        Worlds worlds = Net::getLoginHandler()->getWorlds();
1000
1001
                        if (worlds.size() == 0)
1002
                        {
1003
                            // Trust that the netcode knows what it's doing
1004
                            state = STATE_UPDATE;
1005
                        }
1006
                        else if (worlds.size() == 1)
1007
                        {
1008
                            Net::getLoginHandler()->chooseServer(0);
1009
                            state = STATE_UPDATE;
1010
                        }
1011
                        else
1012
                        {
1013
                            currentDialog = new WorldSelectDialog(worlds);
1014
                            if (options.chooseDefault)
1015
                            {
1016
                                ((WorldSelectDialog*) currentDialog)->action(
1017
                                    gcn::ActionEvent(NULL, "ok"));
1018
                            }
1019
                        }
1020
                    }
1021
                    break;
1022
1023
                case STATE_WORLD_SELECT_ATTEMPT:
1024
                    logger->log("State: WORLD SELECT ATTEMPT");
1025
                    currentDialog = new ConnectionDialog(STATE_WORLD_SELECT);
1026
                    break;
1027
1028
                case STATE_UPDATE:
1029
1030
                    // Determine which source to use for the update host
1031
                    if (!options.updateHost.empty())
1032
                        updateHost = options.updateHost;
1033
                    else
1034
                        updateHost = loginData.updateHost;
1035
                    setUpdatesDir();
1036
1037
                    if (options.skipUpdate)
1038
                    {
1039
                        state = STATE_LOAD_DATA;
1040
                    }
1041
                    else
1042
                    {
1043
                        logger->log("State: UPDATE");
1044
                        currentDialog = new UpdaterWindow(updateHost,
1045
                                homeDir + "/" + updatesDir);
1046
                    }
1047
                    break;
1048
1049
                case STATE_LOAD_DATA:
1050
                    logger->log("State: LOAD DATA");
1051
1052
                    // Load the updates downloaded so far...
1053
                    if (!options.skipUpdateLoad)
1054
                    {
1055
                        loadUpdates();
1056
                    }
1057
1058
                    // Also add customdata directory
1059
                    ResourceManager::getInstance()->searchAndAddArchives(
1060
                            "customdata/",
1061
                            "zip",
1062
                            false);
1063
1064
                    // Load XML databases
1065
                    ColorDB::load();
1066
                    ItemDB::load();
1067
                    Being::load(); // Hairstyles
1068
                    MonsterDB::load();
1069
                    NPCDB::load();
1070
                    EmoteDB::load();
1071
                    StatusEffect::load();
1072
                    Units::loadUnits();
1073
1074
                    desktop->reloadWallpaper();
1075
1076
                    state = STATE_GET_CHARACTERS;
1077
                    break;
1078
1079
                case STATE_GET_CHARACTERS:
1080
                    logger->log("State: GET CHARACTERS");
1081
                    Net::getCharHandler()->getCharacters();
1082
                    break;
1083
1084
                case STATE_CHAR_SELECT:
1085
                    logger->log("State: CHAR SELECT");
1086
                    // Don't allow an alpha opacity
1087
                    // lower than the default value
1088
                    SkinLoader::instance()->setMinimumOpacity(0.8f);
1089
1090
                    currentDialog =
1091
                        new CharSelectDialog(&charInfo, &loginData);
1092
1093
                    if (((CharSelectDialog*) currentDialog)->
1094
                            selectByName(options.character)) {
1095
                        ((CharSelectDialog*) currentDialog)->chooseSelected();
1096
                    } else {
1097
                        ((CharSelectDialog*) currentDialog)->selectByName(
1098
                            config.getValue("lastCharacter", ""));
1099
                    }
1100
1101
                    break;
1102
1103
                case STATE_CONNECT_GAME:
1104
                    logger->log("State: CONNECT GAME");
1105
1106
                    // Allow any alpha opacity
1107
                    SkinLoader::instance()->setMinimumOpacity(-1.0f);
1108
1109
                    // Fade out logon-music here too to give the desired effect
1110
                    // of "flowing" into the game.
1111
                    sound.fadeOutMusic(1000);
1112
1113
                    Net::getGameHandler()->connect();
1114
                    currentDialog = new ConnectionDialog(STATE_SWITCH_CHARACTER);
1115
                    break;
1116
1117
                case STATE_GAME:
1118
                    logger->log("Memorizing selected character %s",
1119
                            player_node->getName().c_str());
1120
                    config.setValue("lastCharacter", player_node->getName());
1121
1122
                    Net::getGameHandler()->inGame();
1123
1124
                    delete setupButton;
1125
                    delete desktop;
1126
                    setupButton = NULL;
1127
                    desktop = NULL;
1128
1129
                    currentDialog = NULL;
1130
1131
                    logger->log("State: GAME");
1132
                    game = new Game;
1133
                    game->logic();
1134
                    delete game;
1135
                    game = 0;
1136
1137
                    state = STATE_EXIT;
1138
1139
                    Net::getGeneralHandler()->unload();
1140
1141
                    break;
1142
1143
                case STATE_LOGIN_ERROR:
1144
                    logger->log("State: LOGIN ERROR");
1145
                    currentDialog = new OkDialog(_("Error"), errorMessage);
1146
                    currentDialog->addActionListener(&loginListener);
1147
                    currentDialog = NULL; // OkDialog deletes itself
1148
                    break;
1149
1150
                case STATE_ACCOUNTCHANGE_ERROR:
1151
                    logger->log("State: ACCOUNT CHANGE ERROR");
1152
                    currentDialog = new OkDialog(_("Error"), errorMessage);
1153
                    currentDialog->addActionListener(&accountListener);
1154
                    currentDialog = NULL; // OkDialog deletes itself
1155
                    break;
1156
1157
                case STATE_REGISTER:
1158
                    logger->log("State: REGISTER");
1159
                    currentDialog = new RegisterDialog(&loginData);
1160
                    break;
1161
1162
                case STATE_REGISTER_ATTEMPT:
1163
                    logger->log("Username is %s", loginData.username.c_str());
1164
1165
                    Net::getCharHandler()->setCharInfo(&charInfo);
1166
                    Net::getLoginHandler()->registerAccount(&loginData);
1167
                    break;
1168
1169
                case STATE_CHANGEPASSWORD:
1170
                    logger->log("State: CHANGE PASSWORD");
1171
                    currentDialog = new ChangePasswordDialog(&loginData);
1172
                    break;
1173
1174
                case STATE_CHANGEPASSWORD_ATTEMPT:
1175
                    logger->log("State: CHANGE PASSWORD ATTEMPT");
1176
                    Net::getLoginHandler()->changePassword(loginData.username,
1177
                                                loginData.password,
1178
                                                loginData.newPassword);
1179
                    break;
1180
1181
                case STATE_CHANGEPASSWORD_SUCCESS:
1182
                    logger->log("State: CHANGE PASSWORD SUCCESS");
1183
                    currentDialog = new OkDialog(_("Password Change"),
1184
                            _("Password changed successfully!"));
1185
                    currentDialog->addActionListener(&accountListener);
1186
                    currentDialog = NULL; // OkDialog deletes itself
1187
                    loginData.password = loginData.newPassword;
1188
                    loginData.newPassword = "";
1189
                    break;
1190
1191
                case STATE_CHANGEEMAIL:
1192
                    logger->log("State: CHANGE EMAIL");
1193
                    currentDialog = new ChangeEmailDialog(&loginData);
1194
                    break;
1195
1196
                case STATE_CHANGEEMAIL_ATTEMPT:
1197
                    logger->log("State: CHANGE EMAIL ATTEMPT");
1198
                    Net::getLoginHandler()->changeEmail(loginData.email);
1199
                    break;
1200
1201
                case STATE_CHANGEEMAIL_SUCCESS:
1202
                    logger->log("State: CHANGE EMAIL SUCCESS");
1203
                    currentDialog = new OkDialog(_("Email Change"),
1204
                            _("Email changed successfully!"));
1205
                    currentDialog->addActionListener(&accountListener);
1206
                    currentDialog = NULL; // OkDialog deletes itself
1207
                    break;
1208
1209
                case STATE_UNREGISTER:
1210
                    logger->log("State: UNREGISTER");
1211
                    currentDialog = new UnRegisterDialog(&loginData);
1212
                    break;
1213
1214
                case STATE_UNREGISTER_ATTEMPT:
1215
                    logger->log("State: UNREGISTER ATTEMPT");
1216
                    Net::getLoginHandler()->unregisterAccount(
1217
                            loginData.username, loginData.password);
1218
                    break;
1219
1220
                case STATE_UNREGISTER_SUCCESS:
1221
                    logger->log("State: UNREGISTER SUCCESS");
1222
#ifdef TMWSERV_SUPPORT
1223
                    accountServerConnection->disconnect();
1224
#endif
1225
                    currentDialog = new OkDialog(_("Unregister Successful"),
1226
                            _("Farewell, come back any time..."));
1227
                    loginData.clear();
1228
                    //The errorlistener sets the state to STATE_CHOOSE_SERVER
1229
                    currentDialog->addActionListener(&errorListener);
1230
                    currentDialog = NULL; // OkDialog deletes itself
1231
                    break;
1232
1233
                case STATE_SWITCH_SERVER:
1234
                    logger->log("State: SWITCH SERVER");
1235
1236
#ifdef TMWSERV_SUPPORT
1237
                    gameServerConnection->disconnect();
1238
                    chatServerConnection->disconnect();
1239
                    accountServerConnection->disconnect();
1240
#endif
1241
1242
                    state = STATE_CHOOSE_SERVER;
1243
                    break;
1244
1245
                case STATE_SWITCH_LOGIN:
1246
                    logger->log("State: SWITCH LOGIN");
1247
1248
                    Net::getLoginHandler()->logout();
1249
1250
                    state = STATE_LOGIN;
1251
                    break;
1252
1253
                case STATE_SWITCH_CHARACTER:
1254
                    logger->log("State: SWITCH CHARACTER");
1255
1256
                    // Done with game
1257
                    Net::getGameHandler()->clear();
1258
1259
                    Net::getCharHandler()->getCharacters();
1260
                    break;
1261
1262
                case STATE_LOGOUT_ATTEMPT:
1263
                    logger->log("State: LOGOUT ATTEMPT");
1264
                    // TODO
1265
                    break;
1266
1267
                case STATE_WAIT:
1268
                    logger->log("State: WAIT");
1269
                    break;
1270
1271
                case STATE_EXIT:
1272
                    logger->log("State: EXIT");
1273
                    break;
1274
1275
                case STATE_FORCE_QUIT:
1276
                    logger->log("State: FORCE QUIT");
1277
                    if (Net::getGeneralHandler())
1278
                        Net::getGeneralHandler()->unload();
1279
                    state = STATE_EXIT;
1280
                  break;
1281
1282
                case STATE_ERROR:
1283
                    logger->log("State: ERROR");
1284
                    currentDialog = new OkDialog(_("Error"), errorMessage);
1285
                    currentDialog->addActionListener(&errorListener);
1286
                    currentDialog = NULL; // OkDialog deletes itself
1287
                    Net::getGameHandler()->clear();
1288
                    Net::getGeneralHandler()->clearHandlers();
1289
                    break;
1290
1291
                default:
1292
                    state = STATE_FORCE_QUIT;
1293
                    break;
1294
            }
1295
        }
1296
1297
        /*
1298
         * This loop can really stress the CPU, for no reason since it's
1299
         * just constantly redrawing the wallpaper.  Added the following
1300
         * usleep to limit it to 40 FPS during the login sequence
1301
         */
1302
        if (!handledEvents)
1303
            usleep(25000);
1304
    }
1305
1306
    delete guiPalette;
1307
/*#ifdef EATHENA_SUPPORT
1308
    delete network;
1309
#endif*/
1310
1311
    logger->log("Quitting");
1312
    exitEngine();
1313
    PHYSFS_deinit();
1314
    delete logger;
1315
1316
    return 0;
1317
}
1318
1319
void SetupListener::action(const gcn::ActionEvent &event)
1320
{
1321
    Window *window = NULL;
1322
1323
    if (event.getId() == "Setup")
1324
        window = setupWindow;
1325
1326
    if (window)
1327
    {
1328
        window->setVisible(!window->isVisible());
1329
        if (window->isVisible())
1330
            window->requestMoveToTop();
1331
    }
1332
}