GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/progs/manaplus/client.cpp Lines: 97 881 11.0 %
Date: 2018-05-19 03:07:18 Branches: 42 1027 4.1 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2018  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "progs/manaplus/client.h"
24
25
#include "chatlogger.h"
26
#include "configmanager.h"
27
#include "dirs.h"
28
#include "eventsmanager.h"
29
#include "game.h"
30
#include "graphicsmanager.h"
31
#include "main.h"
32
#include "party.h"
33
#include "pincodemanager.h"
34
#include "settings.h"
35
#include "soundmanager.h"
36
#include "spellmanager.h"
37
38
#include "being/localclan.h"
39
#include "being/localplayer.h"
40
#include "being/playerinfo.h"
41
#include "being/playerrelations.h"
42
43
#include "const/net/net.h"
44
45
#include "enums/being/attributesstrings.h"
46
47
#include "fs/virtfs/fs.h"
48
#include "fs/virtfs/tools.h"
49
50
#include "gui/dialogsmanager.h"
51
#include "gui/gui.h"
52
#include "gui/skin.h"
53
#include "gui/popupmanager.h"
54
#include "gui/windowmanager.h"
55
56
#include "gui/shortcut/dropshortcut.h"
57
#include "gui/shortcut/emoteshortcut.h"
58
#include "gui/shortcut/itemshortcut.h"
59
#include "gui/shortcut/spellshortcut.h"
60
61
#include "gui/windows/changeemaildialog.h"
62
#include "gui/windows/changepassworddialog.h"
63
#include "gui/windows/charselectdialog.h"
64
#include "gui/windows/connectiondialog.h"
65
#include "gui/windows/equipmentwindow.h"
66
#include "gui/windows/logindialog.h"
67
#include "gui/windows/npcdialog.h"
68
#include "gui/windows/okdialog.h"
69
#include "gui/windows/registerdialog.h"
70
#include "gui/windows/serverdialog.h"
71
#include "gui/windows/setupwindow.h"
72
#include "gui/windows/updaterwindow.h"
73
#include "gui/windows/quitdialog.h"
74
#include "gui/windows/worldselectdialog.h"
75
76
#include "gui/widgets/button.h"
77
#include "gui/widgets/createwidget.h"
78
#include "gui/widgets/desktop.h"
79
#include "gui/widgets/windowcontainer.h"
80
81
#include "input/inputmanager.h"
82
#include "input/joystick.h"
83
#include "input/keyboardconfig.h"
84
85
#include "input/touch/touchmanager.h"
86
87
#include "net/charserverhandler.h"
88
#include "net/chathandler.h"
89
#include "net/download.h"
90
#include "net/gamehandler.h"
91
#include "net/generalhandler.h"
92
#include "net/guildhandler.h"
93
#include "net/inventoryhandler.h"
94
#include "net/ipc.h"
95
#include "net/loginhandler.h"
96
#include "net/net.h"
97
#include "net/updatetypeoperators.h"
98
#include "net/useragent.h"
99
#include "net/packetlimiter.h"
100
#include "net/partyhandler.h"
101
102
#ifdef TMWA_SUPPORT
103
#include "net/tmwa/guildmanager.h"
104
#endif  // TMWA_SUPPORT
105
106
#include "particle/particleengine.h"
107
108
#include "resources/dbmanager.h"
109
#include "resources/imagehelper.h"
110
111
#include "resources/dye/dyepalette.h"
112
113
#include "resources/resourcemanager/resourcemanager.h"
114
115
#include "resources/sprite/spritereference.h"
116
117
#include "utils/checkutils.h"
118
#include "utils/cpu.h"
119
#include "utils/delete2.h"
120
#include "utils/dumplibs.h"
121
#include "utils/dumpsizes.h"
122
#include "utils/env.h"
123
#include "utils/fuzzer.h"
124
#include "utils/gettext.h"
125
#include "utils/gettexthelper.h"
126
#include "utils/mrand.h"
127
#ifdef ANDROID
128
#include "fs/paths.h"
129
#endif  // ANDROID
130
#include "utils/sdlcheckutils.h"
131
#include "utils/sdlhelper.h"
132
#include "utils/timer.h"
133
134
#include "utils/translation/translationmanager.h"
135
136
#include "listeners/assertlistener.h"
137
#include "listeners/errorlistener.h"
138
139
#ifdef USE_OPENGL
140
#include "test/testlauncher.h"
141
#include "test/testmain.h"
142
#else  // USE_OPENGL
143
#include "configuration.h"
144
#endif  // USE_OPENGL
145
146
#ifdef WIN32
147
PRAGMA48(GCC diagnostic push)
148
PRAGMA48(GCC diagnostic ignored "-Wshadow")
149
#include <SDL_syswm.h>
150
PRAGMA48(GCC diagnostic pop)
151
#include "fs/specialfolder.h"
152
#undef ERROR
153
#endif  // WIN32
154
155
#ifdef ANDROID
156
#ifndef USE_SDL2
157
PRAGMA48(GCC diagnostic push)
158
PRAGMA48(GCC diagnostic ignored "-Wshadow")
159
#include <SDL_screenkeyboard.h>
160
PRAGMA48(GCC diagnostic pop)
161
#include <fstream>
162
#endif  // USE_SDL2
163
#endif  // ANDROID
164
165
#include <sys/stat.h>
166
167
#ifdef USE_MUMBLE
168
#include "mumblemanager.h"
169
#endif  // USE_MUMBLE
170
171
PRAGMA48(GCC diagnostic push)
172
PRAGMA48(GCC diagnostic ignored "-Wshadow")
173
#ifdef USE_SDL2
174
#include <SDL2_framerate.h>
175
#else  // USE_SDL2
176
#include <SDL_framerate.h>
177
#endif  // USE_SDL2
178
PRAGMA48(GCC diagnostic pop)
179
180
#include "debug.h"
181
182
2
std::string errorMessage;
183
2
LoginData loginData;
184
185
Client *client = nullptr;
186
187
extern FPSmanager fpsManager;
188
extern int evolPacketOffset;
189
190
volatile bool runCounters;
191
bool isSafeMode = false;
192
int serverVersion = 0;
193
int packetVersion = 0;
194
int packetVersionMain = 0;
195
int packetVersionRe = 0;
196
int packetVersionZero = 0;
197
int packetsType = 0;
198
bool packets_main = true;
199
bool packets_re = false;
200
bool packets_zero = false;
201
unsigned int tmwServerVersion = 0;
202
time_t start_time;
203
unsigned int mLastHost = 0;
204
unsigned long mSearchHash = 0;
205
int textures_count = 0;
206
volatile bool isTerminate = false;
207
208
namespace
209
{
210
2
    class AccountListener final : public ActionListener
211
    {
212
        public:
213
            AccountListener()
214
4
            { }
215
216
            A_DELETE_COPY(AccountListener)
217
218
            void action(const ActionEvent &event A_UNUSED) override final
219
            {
220
                client->setState(State::CHAR_SELECT);
221
            }
222
2
    } accountListener;
223
224
2
    class LoginListener final : public ActionListener
225
    {
226
        public:
227
            LoginListener()
228
4
            { }
229
230
            A_DELETE_COPY(LoginListener)
231
232
            void action(const ActionEvent &event A_UNUSED) override final
233
            {
234
                client->setState(State::PRE_LOGIN);
235
            }
236
2
    } loginListener;
237
}  // namespace
238
239
428
Client::Client() :
240
    ActionListener(),
241
    mCurrentServer(),
242
    mGame(nullptr),
243
    mCurrentDialog(nullptr),
244
    mQuitDialog(nullptr),
245
    mSetupButton(nullptr),
246
    mVideoButton(nullptr),
247
    mHelpButton(nullptr),
248
    mAboutButton(nullptr),
249
    mThemesButton(nullptr),
250
    mPerfomanceButton(nullptr),
251
#ifdef ANDROID
252
    mCloseButton(nullptr),
253
#endif  // ANDROID
254
    mState(State::CHOOSE_SERVER),
255
    mOldState(State::START),
256
    mSkin(nullptr),
257
    mButtonPadding(1),
258
    mButtonSpacing(3),
259
    mPing(0),
260
1284
    mConfigAutoSaved(false)
261
{
262
428
    WindowManager::init();
263
428
}
264
265
void Client::testsInit()
266
{
267
    if (!settings.options.test.empty() &&
268
        settings.options.test != "99")
269
    {
270
        gameInit();
271
    }
272
    else
273
    {
274
        initRand();
275
        logger = new Logger;
276
        SDL::initLogger();
277
        Dirs::initLocalDataDir();
278
        Dirs::initTempDir();
279
        Dirs::initConfigDir();
280
        GettextHelper::initLang();
281
    }
282
}
283
284
void Client::gameInit()
285
{
286
    logger = new Logger;
287
    SDL::initLogger();
288
289
    initRand();
290
291
    assertListener = new AssertListener;
292
    // Load branding information
293
    if (!settings.options.brandingPath.empty())
294
    {
295
        branding.init(settings.options.brandingPath,
296
            UseVirtFs_false,
297
            SkipError_false);
298
    }
299
    setBrandingDefaults(branding);
300
301
    Dirs::initRootDir();
302
    Dirs::initHomeDir();
303
304
    // Configure logger
305
    if (!settings.options.logFileName.empty())
306
    {
307
        settings.logFileName = settings.options.logFileName;
308
    }
309
    else
310
    {
311
        settings.logFileName = pathJoin(settings.localDataDir,
312
            "manaplus.log");
313
    }
314
    logger->log("Log file: " + settings.logFileName);
315
    logger->setLogFile(settings.logFileName);
316
317
#ifdef USE_FUZZER
318
    Fuzzer::init();
319
#endif  // USE_FUZZER
320
321
    if (settings.options.ipc == true)
322
        IPC::start();
323
    if (settings.options.test.empty())
324
        ConfigManager::backupConfig("config.xml");
325
    ConfigManager::initConfiguration();
326
    SDL::setLogLevel(config.getIntValue("sdlLogLevel"));
327
    settings.init();
328
    Net::loadIgnorePackets();
329
    setPathsDefaults(paths);
330
    initFeatures();
331
    initPaths();
332
    logger->log("init 4");
333
    logger->setDebugLog(config.getBoolValue("debugLog"));
334
    logger->setReportUnimplemented(config.getBoolValue("unimplimentedLog"));
335
336
    config.incValue("runcount");
337
338
#ifndef ANDROID
339
    if (settings.options.test.empty())
340
        ConfigManager::storeSafeParameters();
341
#endif  // ANDROID
342
343
    if (!VirtFs::setWriteDir(settings.localDataDir))
344
    {
345
        logger->error(strprintf("%s couldn't be set as home directory! "
346
            "Exiting.", settings.localDataDir.c_str()));
347
    }
348
349
    GettextHelper::initLang();
350
351
    chatLogger = new ChatLogger;
352
    if (settings.options.chatLogDir.empty())
353
    {
354
        chatLogger->setBaseLogDir(settings.localDataDir
355
            + std::string("/logs/"));
356
    }
357
    else
358
    {
359
        chatLogger->setBaseLogDir(settings.options.chatLogDir);
360
    }
361
362
    // Log the client version
363
    logger->log1(FULL_VERSION);
364
    logger->log("Start configPath: " + config.getConfigPath());
365
366
    Dirs::initScreenshotDir();
367
368
    updateEnv();
369
    SDL::allowScreenSaver(config.getBoolValue("allowscreensaver"));
370
    dumpLibs();
371
    dumpSizes();
372
373
    // Initialize SDL
374
    logger->log1("Initializing SDL...");
375
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
376
    {
377
        logger->safeError(strprintf("Could not initialize SDL: %s",
378
            SDL_GetError()));
379
    }
380
    atexit(SDL_Quit);
381
382
    PacketLimiter::initPacketLimiter();
383
#ifndef USE_SDL2
384
    SDL_EnableUNICODE(1);
385
#endif  // USE_SDL2
386
387
    WindowManager::applyKeyRepeat();
388
    eventsManager.init();
389
    eventsManager.enableEvents();
390
391
#ifdef WIN32
392
    Dirs::mountDataDir();
393
#endif  // WIN32
394
#ifndef USE_SDL2
395
    WindowManager::setIcon();
396
#endif  // USE_SDL2
397
398
    ConfigManager::checkConfigVersion();
399
    logVars();
400
    Cpu::detect();
401
    DyePalette::initFunctions();
402
#if defined(USE_OPENGL)
403
#if !defined(ANDROID) && !defined(__APPLE__) && \
404
    !defined(__native_client__) && !defined(UNITTESTS)
405
    if (!settings.options.safeMode &&
406
        settings.options.renderer < 0 &&
407
        settings.options.test.empty() &&
408
        !settings.options.validate &&
409
        !config.getBoolValue("videodetected"))
410
    {
411
        graphicsManager.detectVideoSettings();
412
    }
413
#endif  // !defined(ANDROID) && !defined(__APPLE__) &&
414
        // !defined(__native_client__) && !defined(UNITTESTS)
415
#endif  // defined(USE_OPENGL)
416
417
    initGraphics();
418
    UserAgent::update();
419
420
    touchManager.init();
421
422
#ifndef WIN32
423
    Dirs::extractDataDir();
424
    Dirs::mountDataDir();
425
#endif  // WIN32
426
427
    Dirs::updateDataPath();
428
429
    // Add the main data directories to our VirtFs search path
430
    if (!settings.options.dataPath.empty())
431
    {
432
        VirtFs::mountDir(settings.options.dataPath,
433
            Append_false);
434
    }
435
436
    // Add the local data directory to VirtFs search path
437
    VirtFs::mountDir(settings.localDataDir,
438
        Append_false);
439
    TranslationManager::loadCurrentLang();
440
    TranslationManager::loadDictionaryLang();
441
#ifdef ENABLE_CUSTOMNLS
442
    TranslationManager::loadGettextLang();
443
#endif  // ENABLE_CUSTOMNLS
444
445
#ifdef USE_SDL2
446
    WindowManager::setIcon();
447
#endif  // USE_SDL2
448
    WindowManager::initTitle();
449
450
    mainGraphics->postInit();
451
452
    theme = new Theme;
453
    Theme::selectSkin();
454
    ActorSprite::load();
455
    touchManager.init();
456
457
    // Initialize the item and emote shortcuts.
458
    for (size_t f = 0; f < SHORTCUT_TABS; f ++)
459
        itemShortcut[f] = new ItemShortcut(f);
460
    emoteShortcut = new EmoteShortcut;
461
    dropShortcut = new DropShortcut;
462
463
    gui = new Gui;
464
    gui->postInit(mainGraphics);
465
    dialogsManager = new DialogsManager;
466
    popupManager = new PopupManager;
467
468
    initSoundManager();
469
470
    // Initialize keyboard
471
    keyboard.init();
472
    inputManager.init();
473
474
    // Initialise player relations
475
    playerRelations.init();
476
    Joystick::init();
477
    WindowManager::createWindows();
478
479
    keyboard.update();
480
    if (joystick != nullptr)
481
        joystick->update();
482
483
    // Initialize default server
484
    mCurrentServer.hostname = settings.options.serverName;
485
    mCurrentServer.port = settings.options.serverPort;
486
    if (!settings.options.serverType.empty())
487
    {
488
        mCurrentServer.type = ServerInfo::parseType(
489
            settings.options.serverType);
490
    }
491
492
    loginData.username = settings.options.username;
493
    loginData.password = settings.options.password;
494
    LoginDialog::savedPassword = settings.options.password;
495
    loginData.remember = (serverConfig.getValue("remember", 1) != 0);
496
    loginData.registerLogin = false;
497
498
    if (mCurrentServer.hostname.empty())
499
    {
500
        mCurrentServer.hostname = branding.getValue("defaultServer", "");
501
        settings.options.serverName = mCurrentServer.hostname;
502
    }
503
504
    if (mCurrentServer.port == 0)
505
    {
506
        mCurrentServer.port = CAST_U16(branding.getValue(
507
            "defaultPort", CAST_S32(DEFAULT_PORT)));
508
        mCurrentServer.type = ServerInfo::parseType(
509
            branding.getValue("defaultServerType", "tmwathena"));
510
    }
511
512
    chatLogger->setServerName(mCurrentServer.hostname);
513
514
    if (loginData.username.empty() && loginData.remember)
515
        loginData.username = serverConfig.getValue("username", "");
516
517
    if (mState != State::ERROR)
518
        mState = State::CHOOSE_SERVER;
519
520
    startTimers();
521
522
    const int fpsLimit = config.getIntValue("fpslimit");
523
    settings.limitFps = fpsLimit > 0;
524
525
    SDL_initFramerate(&fpsManager);
526
    WindowManager::setFramerate(fpsLimit);
527
    initConfigListeners();
528
529
    settings.guiAlpha = config.getFloatValue("guialpha");
530
    optionChanged("fpslimit");
531
532
    start_time = time(nullptr);
533
534
    PlayerInfo::init();
535
536
#ifdef ANDROID
537
#ifndef USE_SDL2
538
    WindowManager::updateScreenKeyboard(SDL_GetScreenKeyboardHeight(nullptr));
539
#endif  // USE_SDL2
540
#endif  // ANDROID
541
542
#ifdef USE_MUMBLE
543
    if (!mumbleManager)
544
        mumbleManager = new MumbleManager;
545
#endif  // USE_MUMBLE
546
547
    mSkin = theme->load("windowmenu.xml",
548
        "",
549
        true,
550
        Theme::getThemePath());
551
    if (mSkin != nullptr)
552
    {
553
        mButtonPadding = mSkin->getPadding();
554
        mButtonSpacing = mSkin->getOption("spacing", 3);
555
    }
556
    if (settings.options.error)
557
        inputManager.executeAction(InputAction::ERROR);
558
559
    if (settings.options.validate == true)
560
        runValidate();
561
}
562
563
1284
Client::~Client()
564
{
565
428
    if (!settings.options.testMode)
566
428
        gameClear();
567
    else
568
        testsClear();
569
    CHECKLISTENERS
570
856
}
571
572
void Client::initConfigListeners()
573
{
574
    config.addListener("fpslimit", this);
575
    config.addListener("guialpha", this);
576
    config.addListener("gamma", this);
577
    config.addListener("enableGamma", this);
578
    config.addListener("particleEmitterSkip", this);
579
    config.addListener("vsync", this);
580
    config.addListener("repeateDelay", this);
581
    config.addListener("repeateInterval", this);
582
    config.addListener("logInput", this);
583
}
584
585
void Client::initSoundManager()
586
{
587
    // Initialize sound engine
588
    try
589
    {
590
        if (config.getBoolValue("sound"))
591
            soundManager.init();
592
593
        soundManager.setSfxVolume(config.getIntValue("sfxVolume"));
594
        soundManager.setMusicVolume(config.getIntValue("musicVolume"));
595
    }
596
    catch (const char *const err)
597
    {
598
        mState = State::ERROR;
599
        errorMessage = err;
600
        logger->log("Warning: %s", err);
601
    }
602
    soundManager.playMusic(branding.getValue(
603
        "loginMusic",
604
        "keprohm.ogg"),
605
        SkipError_true);
606
}
607
608
void Client::initGraphics()
609
{
610
#ifndef USE_SDL2
611
    WindowManager::applyVSync();
612
#endif  // USE_SDL2
613
614
    runCounters = config.getBoolValue("packetcounters");
615
616
    graphicsManager.initGraphics();
617
#ifdef USE_SDL2
618
    WindowManager::applyVSync();
619
#endif  // USE_SDL2
620
621
    imageHelper->postInit();
622
    setConfigDefaults2(config);
623
    WindowManager::applyGrabMode();
624
    WindowManager::applyGamma();
625
626
    mainGraphics->beginDraw();
627
}
628
629
void Client::testsClear()
630
{
631
    if (!settings.options.test.empty())
632
        gameClear();
633
    else
634
        BeingInfo::clear();
635
}
636
637
428
void Client::gameClear()
638
{
639
428
    if (logger != nullptr)
640
428
        logger->log1("Quitting1");
641
428
    isTerminate = true;
642
428
    config.removeListeners(this);
643
644
428
    delete2(assertListener);
645
646
428
    IPC::stop();
647
428
    eventsManager.shutdown();
648
428
    WindowManager::deleteWindows();
649
428
    if (windowContainer != nullptr)
650
252
        windowContainer->slowLogic();
651
652
428
    stopTimers();
653
428
    DbManager::unloadDb();
654
655
428
    if (loginHandler != nullptr)
656
        loginHandler->clearWorlds();
657
658
428
    if (chatHandler != nullptr)
659
        chatHandler->clear();
660
661
428
    if (charServerHandler != nullptr)
662
154
        charServerHandler->clear();
663
664
428
    delete2(ipc);
665
666
#ifdef USE_MUMBLE
667
428
    delete2(mumbleManager);
668
#endif  // USE_MUMBLE
669
670
428
    PlayerInfo::deinit();
671
672
    // Before config.write() since it writes the shortcuts to the config
673
2568
    for (unsigned f = 0; f < SHORTCUT_TABS; f ++)
674
2140
        delete2(itemShortcut[f])
675
428
    delete2(emoteShortcut);
676
428
    delete2(dropShortcut);
677
678
428
    playerRelations.store();
679
680
428
    if (logger != nullptr)
681
428
        logger->log1("Quitting2");
682
683
428
    delete2(mCurrentDialog);
684
428
    delete2(popupManager);
685
428
    delete2(dialogsManager);
686
428
    delete2(gui);
687
688
428
    if (inventoryHandler != nullptr)
689
154
        inventoryHandler->clear();
690
691
428
    if (logger != nullptr)
692
428
        logger->log1("Quitting3");
693
694
428
    touchManager.clear();
695
696
428
    GraphicsManager::deleteRenderers();
697
698
428
    if (logger != nullptr)
699
428
        logger->log1("Quitting4");
700
701
428
    XML::cleanupXML();
702
703
428
    if (logger != nullptr)
704
428
        logger->log1("Quitting5");
705
706
428
    BeingInfo::clear();
707
708
    // Shutdown sound
709
428
    soundManager.close();
710
711
428
    if (logger != nullptr)
712
428
        logger->log1("Quitting6");
713
714
428
    ActorSprite::unload();
715
716
428
    ResourceManager::deleteInstance();
717
718
428
    soundManager.shutdown();
719
720
428
    if (logger != nullptr)
721
428
        logger->log1("Quitting8");
722
723
428
    WindowManager::deleteIcon();
724
725
428
    if (logger != nullptr)
726
428
        logger->log1("Quitting9");
727
728
428
    delete2(joystick);
729
730
428
    keyboard.deinit();
731
732
428
    if (logger != nullptr)
733
428
        logger->log1("Quitting10");
734
735
428
    touchManager.shutdown();
736
737
#ifdef DEBUG_CONFIG
738
    config.enableKeyLogging();
739
#endif  // DEBUG_CONFIG
740
741
428
    config.removeOldKeys();
742
428
    config.write();
743
428
    serverConfig.write();
744
745
428
    config.clear();
746
428
    serverConfig.clear();
747
748
428
    if (logger != nullptr)
749
428
        logger->log1("Quitting11");
750
751
#ifdef USE_PROFILER
752
    Perfomance::clear();
753
#endif  // USE_PROFILER
754
755
#ifdef DEBUG_OPENGL_LEAKS
756
    if (logger)
757
        logger->log("textures left: %d", textures_count);
758
#endif  // DEBUG_OPENGL_LEAKS
759
760
428
    Graphics::cleanUp();
761
762
428
    if (logger != nullptr)
763
428
        logger->log1("Quitting12");
764
765
428
    delete2(chatLogger);
766
428
    TranslationManager::close();
767
428
}
768
769
int Client::testsExec()
770
{
771
#ifdef USE_OPENGL
772
    if (settings.options.test.empty())
773
    {
774
        TestMain test;
775
        return test.exec();
776
    }
777
    else
778
    {
779
        TestLauncher launcher(settings.options.test);
780
        return launcher.exec();
781
    }
782
#else  // USE_OPENGL
783
784
    return 0;
785
#endif  // USE_OPENGL
786
}
787
788
#define ADDBUTTON(var, object) var = object; \
789
    x -= var->getWidth() + mButtonSpacing; \
790
    var->setPosition(x, mButtonPadding); \
791
    top->add(var);
792
793
void Client::stateConnectGame1()
794
{
795
    if ((gameHandler != nullptr) &&
796
        (loginHandler != nullptr) &&
797
        gameHandler->isConnected())
798
    {
799
        loginHandler->disconnect();
800
    }
801
}
802
803
void Client::stateConnectServer1()
804
{
805
    if (mOldState == State::CHOOSE_SERVER)
806
    {
807
        settings.serverName = mCurrentServer.hostname;
808
        ConfigManager::initServerConfig(mCurrentServer.hostname);
809
        PacketLimiter::initPacketLimiter();
810
        initTradeFilter();
811
        Dirs::initUsersDir();
812
        playerRelations.init();
813
814
        // Initialize the item and emote shortcuts.
815
        for (unsigned f = 0; f < SHORTCUT_TABS; f ++)
816
        {
817
            delete itemShortcut[f];
818
            itemShortcut[f] = new ItemShortcut(f);
819
        }
820
        delete emoteShortcut;
821
        emoteShortcut = new EmoteShortcut;
822
823
        // Initialize the drop shortcuts.
824
        delete dropShortcut;
825
        dropShortcut = new DropShortcut;
826
827
        initFeatures();
828
        PlayerInfo::loadData();
829
        loginData.registerUrl = mCurrentServer.registerUrl;
830
        loginData.packetVersion = mCurrentServer.packetVersion;
831
        if (!mCurrentServer.onlineListUrl.empty())
832
            settings.onlineListUrl = mCurrentServer.onlineListUrl;
833
        else
834
            settings.onlineListUrl = settings.serverName;
835
        settings.persistentIp = mCurrentServer.persistentIp;
836
        settings.supportUrl = mCurrentServer.supportUrl;
837
        settings.updateMirrors = mCurrentServer.updateMirrors;
838
        settings.enableRemoteCommands = (serverConfig.getValue(
839
            "enableRemoteCommands", 1) != 0);
840
841
        if (settings.options.username.empty())
842
        {
843
            if (loginData.remember)
844
                loginData.username = serverConfig.getValue("username", "");
845
            else
846
                loginData.username.clear();
847
        }
848
        else
849
        {
850
            loginData.username = settings.options.username;
851
        }
852
        settings.login = loginData.username;
853
        WindowManager::updateTitle();
854
855
        loginData.remember = (serverConfig.getValue("remember", 1) != 0);
856
        Net::connectToServer(mCurrentServer);
857
858
#ifdef USE_MUMBLE
859
        if (mumbleManager)
860
            mumbleManager->setServer(mCurrentServer.hostname);
861
#endif  // USE_MUMBLE
862
863
#ifdef TMWA_SUPPORT
864
        GuildManager::init();
865
#endif  // TMWA_SUPPORT
866
867
        if (!mConfigAutoSaved)
868
        {
869
            mConfigAutoSaved = true;
870
            config.write();
871
        }
872
    }
873
    else if (mOldState != State::CHOOSE_SERVER &&
874
             (loginHandler != nullptr) &&
875
             loginHandler->isConnected())
876
    {
877
        mState = State::PRE_LOGIN;
878
    }
879
}
880
881
void Client::stateWorldSelect1()
882
{
883
    if (mOldState == State::UPDATE &&
884
        (loginHandler != nullptr))
885
    {
886
        if (loginHandler->getWorlds().size() < 2)
887
            mState = State::PRE_LOGIN;
888
    }
889
}
890
891
void Client::stateGame1()
892
{
893
    if (gui == nullptr)
894
        return;
895
896
    BasicContainer2 *const top = static_cast<BasicContainer2*>(
897
        gui->getTop());
898
899
    if (top == nullptr)
900
        return;
901
902
    CREATEWIDGETV(desktop, Desktop, nullptr);
903
    top->add(desktop);
904
    int x = top->getWidth() - mButtonPadding;
905
    ADDBUTTON(mSetupButton, new Button(desktop,
906
        // TRANSLATORS: setup tab quick button
907
        _("Setup"), "Setup", BUTTON_SKIN, this))
908
    ADDBUTTON(mPerfomanceButton, new Button(desktop,
909
        // TRANSLATORS: perfoamance tab quick button
910
        _("Performance"), "Perfomance", BUTTON_SKIN, this))
911
    ADDBUTTON(mVideoButton, new Button(desktop,
912
        // TRANSLATORS: video tab quick button
913
        _("Video"), "Video", BUTTON_SKIN, this))
914
    ADDBUTTON(mThemesButton, new Button(desktop,
915
        // TRANSLATORS: theme tab quick button
916
        _("Theme"), "Themes", BUTTON_SKIN, this))
917
    ADDBUTTON(mAboutButton, new Button(desktop,
918
        // TRANSLATORS: theme tab quick button
919
        _("About"), "about", BUTTON_SKIN, this))
920
    ADDBUTTON(mHelpButton, new Button(desktop,
921
        // TRANSLATORS: theme tab quick button
922
        _("Help"), "help", BUTTON_SKIN, this))
923
#ifdef ANDROID
924
    ADDBUTTON(mCloseButton, new Button(desktop,
925
        // TRANSLATORS: close quick button
926
        _("Close"), "close", BUTTON_SKIN, this))
927
#endif  // ANDROID
928
929
    desktop->setSize(mainGraphics->getWidth(),
930
        mainGraphics->getHeight());
931
}
932
933
void Client::stateSwitchLogin1()
934
{
935
    if (mOldState == State::GAME &&
936
        (gameHandler != nullptr))
937
    {
938
        gameHandler->disconnect();
939
    }
940
}
941
942
int Client::gameExec()
943
{
944
    int lastTickTime = tick_time;
945
946
    while (mState != State::EXIT)
947
    {
948
        PROFILER_START();
949
        if (eventsManager.handleEvents())
950
            continue;
951
952
        BLOCK_START("Client::gameExec 3")
953
        if (generalHandler != nullptr)
954
            generalHandler->flushNetwork();
955
        BLOCK_END("Client::gameExec 3")
956
957
        BLOCK_START("Client::gameExec 4")
958
        if (gui != nullptr)
959
            gui->logic();
960
        cur_time = time(nullptr);
961
        int k = 0;
962
        while (lastTickTime != tick_time &&
963
               k < 40)
964
        {
965
            if (mGame != nullptr)
966
                mGame->logic();
967
            else if (gui != nullptr)
968
                gui->handleInput();
969
970
            ++lastTickTime;
971
            k ++;
972
        }
973
        soundManager.logic();
974
975
        logic_count += k;
976
        if (gui != nullptr)
977
            gui->slowLogic();
978
        if (mGame != nullptr)
979
            mGame->slowLogic();
980
        slowLogic();
981
        BLOCK_END("Client::gameExec 4")
982
983
        // This is done because at some point tick_time will wrap.
984
        lastTickTime = tick_time;
985
986
        // Update the screen when application is visible, delay otherwise.
987
        if (!WindowManager::getIsMinimized())
988
        {
989
            frame_count++;
990
            if (gui != nullptr)
991
                gui->draw();
992
            mainGraphics->updateScreen();
993
        }
994
        else
995
        {
996
            SDL_Delay(100);
997
        }
998
999
        BLOCK_START("~Client::SDL_framerateDelay")
1000
        if (settings.limitFps)
1001
            SDL_framerateDelay(&fpsManager);
1002
        BLOCK_END("~Client::SDL_framerateDelay")
1003
1004
        BLOCK_START("Client::gameExec 6")
1005
        if (mState == State::CONNECT_GAME)
1006
        {
1007
            stateConnectGame1();
1008
        }
1009
        else if (mState == State::CONNECT_SERVER)
1010
        {
1011
            stateConnectServer1();
1012
        }
1013
        else if (mState == State::WORLD_SELECT)
1014
        {
1015
            stateWorldSelect1();
1016
        }
1017
        else if (mOldState == State::START ||
1018
                 (mOldState == State::GAME && mState != State::GAME))
1019
        {
1020
            stateGame1();
1021
        }
1022
        else if (mState == State::SWITCH_LOGIN)
1023
        {
1024
            stateSwitchLogin1();
1025
        }
1026
        BLOCK_END("Client::gameExec 6")
1027
1028
        if (mState != mOldState)
1029
        {
1030
            BLOCK_START("Client::gameExec 7")
1031
            PlayerInfo::stateChange(mState);
1032
1033
            if (mOldState == State::GAME)
1034
            {
1035
                delete2(mGame);
1036
                assertListener = new AssertListener;
1037
                Game::clearInstance();
1038
                ResourceManager::cleanOrphans(false);
1039
                Party::clearParties();
1040
                Guild::clearGuilds();
1041
                NpcDialog::clearDialogs();
1042
                if (guildHandler != nullptr)
1043
                    guildHandler->clear();
1044
                if (partyHandler != nullptr)
1045
                    partyHandler->clear();
1046
                if (chatLogger != nullptr)
1047
                    chatLogger->clear();
1048
                if (!settings.options.dataPath.empty())
1049
                    UpdaterWindow::unloadMods(settings.options.dataPath);
1050
                else
1051
                    UpdaterWindow::unloadMods(settings.oldUpdates);
1052
                if (!settings.options.skipUpdate)
1053
                    UpdaterWindow::unloadMods(settings.oldUpdates + "/fix/");
1054
            }
1055
            else if (mOldState == State::CHAR_SELECT)
1056
            {
1057
                if (mState != State::CHANGEPASSWORD &&
1058
                    charServerHandler != nullptr)
1059
                {
1060
                    charServerHandler->clear();
1061
                }
1062
            }
1063
1064
            mOldState = mState;
1065
1066
            // Get rid of the dialog of the previous state
1067
            delete2(mCurrentDialog);
1068
1069
            // State has changed, while the quitDialog was active, it might
1070
            // not be correct anymore
1071
            if (mQuitDialog != nullptr)
1072
            {
1073
                mQuitDialog->scheduleDelete();
1074
                mQuitDialog = nullptr;
1075
            }
1076
            BLOCK_END("Client::gameExec 7")
1077
1078
            BLOCK_START("Client::gameExec 8")
1079
            switch (mState)
1080
            {
1081
                case State::CHOOSE_SERVER:
1082
                {
1083
                    BLOCK_START("Client::gameExec STATE_CHOOSE_SERVER")
1084
                    logger->log1("State: CHOOSE SERVER");
1085
                    unloadData();
1086
                    pincodeManager.closeDialogs();
1087
1088
                    // Allow changing this using a server choice dialog
1089
                    // We show the dialog box only if the command-line
1090
                    // options weren't set.
1091
                    if (settings.options.serverName.empty() &&
1092
                        settings.options.serverPort == 0 &&
1093
                        !branding.getValue("onlineServerList", "a").empty())
1094
                    {
1095
                        // Don't allow an alpha opacity
1096
                        // lower than the default value
1097
                        theme->setMinimumOpacity(0.8F);
1098
1099
                        CREATEWIDGETV(mCurrentDialog, ServerDialog,
1100
                            &mCurrentServer,
1101
                            settings.configDir);
1102
                    }
1103
                    else
1104
                    {
1105
                        mState = State::CONNECT_SERVER;
1106
1107
                        // Reset options so that cancelling or connect
1108
                        // timeout will show the server dialog.
1109
                        settings.options.serverName.clear();
1110
                        settings.options.serverPort = 0;
1111
                    }
1112
                    BLOCK_END("Client::gameExec STATE_CHOOSE_SERVER")
1113
                    break;
1114
                }
1115
1116
                case State::CONNECT_SERVER:
1117
                    BLOCK_START("Client::gameExec State::CONNECT_SERVER")
1118
                    logger->log1("State: CONNECT SERVER");
1119
                    loginData.updateHosts.clear();
1120
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1121
                        // TRANSLATORS: connection dialog header
1122
                        _("Connecting to server"),
1123
                        State::SWITCH_SERVER);
1124
                    TranslationManager::loadCurrentLang();
1125
                    TranslationManager::loadDictionaryLang();
1126
                    pincodeManager.init();
1127
                    BLOCK_END("Client::gameExec State::CONNECT_SERVER")
1128
                    break;
1129
1130
                case State::PRE_LOGIN:
1131
                    logger->log1("State: PRE_LOGIN");
1132
                    break;
1133
1134
                case State::LOGIN:
1135
                    BLOCK_START("Client::gameExec State::LOGIN")
1136
                    logger->log1("State: LOGIN");
1137
                    // Don't allow an alpha opacity
1138
                    // lower than the default value
1139
                    theme->setMinimumOpacity(0.8F);
1140
1141
                    if (packetVersion == 0)
1142
                    {
1143
                        packetVersion = loginData.packetVersion;
1144
                        if (packetVersion != 0)
1145
                        {
1146
                            loginHandler->updatePacketVersion();
1147
                            logger->log("Preconfigured packet version: %d",
1148
                                packetVersion);
1149
                        }
1150
                    }
1151
1152
                    loginData.updateType = static_cast<UpdateTypeT>(
1153
                        serverConfig.getValue("updateType", 0));
1154
1155
                    mSearchHash = Net::Download::adlerBuffer(
1156
                        const_cast<char*>(mCurrentServer.hostname.c_str()),
1157
                        CAST_S32(mCurrentServer.hostname.size()));
1158
                    if (settings.options.username.empty() ||
1159
                        settings.options.password.empty())
1160
                    {
1161
                        CREATEWIDGETV(mCurrentDialog, LoginDialog,
1162
                            loginData,
1163
                            &mCurrentServer,
1164
                            &settings.options.updateHost);
1165
                    }
1166
                    else
1167
                    {
1168
                        mState = State::LOGIN_ATTEMPT;
1169
                        // Clear the password so that when login fails, the
1170
                        // dialog will show up next time.
1171
                        settings.options.password.clear();
1172
                    }
1173
                    BLOCK_END("Client::gameExec State::LOGIN")
1174
                    break;
1175
1176
                case State::LOGIN_ATTEMPT:
1177
                    BLOCK_START("Client::gameExec State::LOGIN_ATTEMPT")
1178
                    logger->log1("State: LOGIN ATTEMPT");
1179
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1180
                        // TRANSLATORS: connection dialog header
1181
                        _("Logging in"),
1182
                        State::SWITCH_SERVER);
1183
                    if (loginHandler != nullptr)
1184
                        loginHandler->loginOrRegister(&loginData);
1185
                    BLOCK_END("Client::gameExec State::LOGIN_ATTEMPT")
1186
                    break;
1187
1188
                case State::WORLD_SELECT:
1189
                    BLOCK_START("Client::gameExec State::WORLD_SELECT")
1190
                    logger->log1("State: WORLD SELECT");
1191
                    {
1192
                        TranslationManager::loadCurrentLang();
1193
                        TranslationManager::loadDictionaryLang();
1194
                        if (loginHandler == nullptr)
1195
                        {
1196
                            BLOCK_END("Client::gameExec State::WORLD_SELECT")
1197
                            break;
1198
                        }
1199
                        Worlds worlds = loginHandler->getWorlds();
1200
1201
                        if (worlds.empty())
1202
                        {
1203
                            // Trust that the netcode knows what it's doing
1204
                            mState = State::UPDATE;
1205
                        }
1206
                        else if (worlds.size() == 1)
1207
                        {
1208
                            loginHandler->chooseServer(
1209
                                0, mCurrentServer.persistentIp);
1210
                            mState = State::UPDATE;
1211
                        }
1212
                        else
1213
                        {
1214
                            CREATEWIDGETV(mCurrentDialog, WorldSelectDialog,
1215
                                worlds);
1216
                            if (settings.options.chooseDefault)
1217
                            {
1218
                                static_cast<WorldSelectDialog*>(mCurrentDialog)
1219
                                    ->action(ActionEvent(nullptr, "ok"));
1220
                            }
1221
                        }
1222
                    }
1223
                    BLOCK_END("Client::gameExec State::WORLD_SELECT")
1224
                    break;
1225
1226
                case State::WORLD_SELECT_ATTEMPT:
1227
                    BLOCK_START("Client::gameExec State::WORLD_SELECT_ATTEMPT")
1228
                    logger->log1("State: WORLD SELECT ATTEMPT");
1229
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1230
                        // TRANSLATORS: connection dialog header
1231
                        _("Entering game world"),
1232
                        State::WORLD_SELECT);
1233
                    BLOCK_END("Client::gameExec State::WORLD_SELECT_ATTEMPT")
1234
                    break;
1235
1236
                case State::UPDATE:
1237
                    BLOCK_START("Client::gameExec State::UPDATE")
1238
                    logger->log1("State: UPDATE");
1239
1240
                    // Determine which source to use for the update host
1241
                    if (!settings.options.updateHost.empty())
1242
                        settings.updateHost = settings.options.updateHost;
1243
                    else
1244
                        settings.updateHost = loginData.updateHost;
1245
                    Dirs::initUpdatesDir();
1246
1247
                    if (!settings.oldUpdates.empty())
1248
                        UpdaterWindow::unloadUpdates(settings.oldUpdates);
1249
1250
                    if (settings.options.skipUpdate)
1251
                    {
1252
                        mState = State::LOAD_DATA;
1253
                        settings.oldUpdates.clear();
1254
                        UpdaterWindow::loadDirMods(settings.options.dataPath);
1255
                    }
1256
                    else if ((loginData.updateType & UpdateType::Skip) != 0)
1257
                    {
1258
                        settings.oldUpdates = pathJoin(settings.localDataDir,
1259
                            settings.updatesDir);
1260
                        UpdaterWindow::loadLocalUpdates(settings.oldUpdates);
1261
                        mState = State::LOAD_DATA;
1262
                    }
1263
                    else
1264
                    {
1265
                        settings.oldUpdates = pathJoin(settings.localDataDir,
1266
                            settings.updatesDir);
1267
                        CREATEWIDGETV(mCurrentDialog, UpdaterWindow,
1268
                            settings.updateHost,
1269
                            settings.oldUpdates,
1270
                            settings.options.dataPath.empty(),
1271
                            loginData.updateType);
1272
                    }
1273
                    BLOCK_END("Client::gameExec State::UPDATE")
1274
                    break;
1275
1276
                case State::LOAD_DATA:
1277
                {
1278
                    BLOCK_START("Client::gameExec State::LOAD_DATA")
1279
                    logger->log1("State: LOAD DATA");
1280
1281
                    loadData();
1282
1283
                    mState = State::GET_CHARACTERS;
1284
                    BLOCK_END("Client::gameExec State::LOAD_DATA")
1285
                    break;
1286
                }
1287
                case State::GET_CHARACTERS:
1288
                    BLOCK_START("Client::gameExec State::GET_CHARACTERS")
1289
                    logger->log1("State: GET CHARACTERS");
1290
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1291
                        // TRANSLATORS: connection dialog header
1292
                        _("Requesting characters"),
1293
                        State::SWITCH_SERVER);
1294
                    if (charServerHandler != nullptr)
1295
                        charServerHandler->requestCharacters();
1296
                    BLOCK_END("Client::gameExec State::GET_CHARACTERS")
1297
                    break;
1298
1299
                case State::CHAR_SELECT:
1300
                    BLOCK_START("Client::gameExec State::CHAR_SELECT")
1301
                    logger->log1("State: CHAR SELECT");
1302
                    // Don't allow an alpha opacity
1303
                    // lower than the default value
1304
                    theme->setMinimumOpacity(0.8F);
1305
1306
                    settings.login = loginData.username;
1307
                    WindowManager::updateTitle();
1308
1309
                    CREATEWIDGETV(mCurrentDialog, CharSelectDialog,
1310
                        loginData);
1311
                    pincodeManager.updateState();
1312
1313
                    if (!(static_cast<CharSelectDialog*>(mCurrentDialog))
1314
                        ->selectByName(settings.options.character,
1315
                        CharSelectDialog::Choose))
1316
                    {
1317
                        (static_cast<CharSelectDialog*>(mCurrentDialog))
1318
                            ->selectByName(
1319
                            serverConfig.getValue("lastCharacter", ""),
1320
                            settings.options.chooseDefault ?
1321
                            CharSelectDialog::Choose :
1322
                            CharSelectDialog::Focus);
1323
                    }
1324
1325
                    // Choosing character on the command line should work only
1326
                    // once, clear it so that 'switch character' works.
1327
                    settings.options.character.clear();
1328
                    BLOCK_END("Client::gameExec State::CHAR_SELECT")
1329
                    break;
1330
1331
                case State::CONNECT_GAME:
1332
                    BLOCK_START("Client::gameExec State::CONNECT_GAME")
1333
                    logger->log1("State: CONNECT GAME");
1334
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1335
                        // TRANSLATORS: connection dialog header
1336
                        _("Connecting to the game server"),
1337
                        State::CHOOSE_SERVER);
1338
                    if (gameHandler != nullptr)
1339
                        gameHandler->connect();
1340
                    BLOCK_END("Client::gameExec State::CONNECT_GAME")
1341
                    break;
1342
1343
                case State::CHANGE_MAP:
1344
                    BLOCK_START("Client::gameExec State::CHANGE_MAP")
1345
                    logger->log1("State: CHANGE_MAP");
1346
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1347
                        // TRANSLATORS: connection dialog header
1348
                        _("Changing game servers"),
1349
                        State::SWITCH_CHARACTER);
1350
                    if (gameHandler != nullptr)
1351
                        gameHandler->connect();
1352
                    BLOCK_END("Client::gameExec State::CHANGE_MAP")
1353
                    break;
1354
1355
                case State::GAME:
1356
                    BLOCK_START("Client::gameExec State::GAME")
1357
                    if (localPlayer != nullptr)
1358
                    {
1359
                        logger->log("Memorizing selected character %s",
1360
                            localPlayer->getName().c_str());
1361
                        serverConfig.setValue("lastCharacter",
1362
                            localPlayer->getName());
1363
#ifdef USE_MUMBLE
1364
                        if (mumbleManager)
1365
                            mumbleManager->setPlayer(localPlayer->getName());
1366
#endif  // USE_MUMBLE
1367
                    }
1368
1369
                    // Fade out logon-music here too to give the desired effect
1370
                    // of "flowing" into the game.
1371
                    soundManager.fadeOutMusic(1000);
1372
1373
                    // Allow any alpha opacity
1374
                    theme->setMinimumOpacity(-1.0F);
1375
1376
                    if (chatLogger != nullptr)
1377
                        chatLogger->setServerName(settings.serverName);
1378
1379
#ifdef ANDROID
1380
                    delete2(mCloseButton);
1381
#endif  // ANDROID
1382
1383
                    delete2(mSetupButton);
1384
                    delete2(mVideoButton);
1385
                    delete2(mThemesButton);
1386
                    delete2(mAboutButton);
1387
                    delete2(mHelpButton);
1388
                    delete2(mPerfomanceButton);
1389
                    delete2(desktop);
1390
1391
                    mCurrentDialog = nullptr;
1392
1393
                    logger->log1("State: GAME");
1394
                    if (generalHandler != nullptr)
1395
                        generalHandler->reloadPartially();
1396
                    mGame = new Game;
1397
                    BLOCK_END("Client::gameExec State::GAME")
1398
                    break;
1399
1400
                case State::LOGIN_ERROR:
1401
                    BLOCK_START("Client::gameExec State::LOGIN_ERROR")
1402
                    logger->log1("State: LOGIN ERROR");
1403
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1404
                        // TRANSLATORS: error dialog header
1405
                        _("Error"),
1406
                        errorMessage,
1407
                        // TRANSLATORS: ok dialog button
1408
                        _("Close"),
1409
                        DialogType::ERROR,
1410
                        Modal_true,
1411
                        ShowCenter_true,
1412
                        nullptr,
1413
                        260);
1414
                    mCurrentDialog->addActionListener(&loginListener);
1415
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1416
                    BLOCK_END("Client::gameExec State::LOGIN_ERROR")
1417
                    break;
1418
1419
                case State::ACCOUNTCHANGE_ERROR:
1420
                    BLOCK_START("Client::gameExec State::ACCOUNTCHANGE_ERROR")
1421
                    logger->log1("State: ACCOUNT CHANGE ERROR");
1422
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1423
                        // TRANSLATORS: error dialog header
1424
                        _("Error"),
1425
                        errorMessage,
1426
                        // TRANSLATORS: ok dialog button
1427
                        _("Close"),
1428
                        DialogType::ERROR,
1429
                        Modal_true,
1430
                        ShowCenter_true,
1431
                        nullptr,
1432
                        260);
1433
                    mCurrentDialog->addActionListener(&accountListener);
1434
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1435
                    BLOCK_END("Client::gameExec State::ACCOUNTCHANGE_ERROR")
1436
                    break;
1437
1438
                case State::REGISTER_PREP:
1439
                    BLOCK_START("Client::gameExec State::REGISTER_PREP")
1440
                    logger->log1("State: REGISTER_PREP");
1441
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1442
                        // TRANSLATORS: connection dialog header
1443
                        _("Requesting registration details"),
1444
                        State::LOGIN);
1445
                    loginHandler->getRegistrationDetails();
1446
                    BLOCK_END("Client::gameExec State::REGISTER_PREP")
1447
                    break;
1448
1449
                case State::REGISTER:
1450
                    logger->log1("State: REGISTER");
1451
                    CREATEWIDGETV(mCurrentDialog, RegisterDialog,
1452
                        loginData);
1453
                    break;
1454
1455
                case State::REGISTER_ATTEMPT:
1456
                    BLOCK_START("Client::gameExec State::REGISTER_ATTEMPT")
1457
                    logger->log("Username is %s", loginData.username.c_str());
1458
                    if (loginHandler != nullptr)
1459
                        loginHandler->registerAccount(&loginData);
1460
                    BLOCK_END("Client::gameExec State::REGISTER_ATTEMPT")
1461
                    break;
1462
1463
                case State::CHANGEPASSWORD:
1464
                    BLOCK_START("Client::gameExec State::CHANGEPASSWORD")
1465
                    logger->log1("State: CHANGE PASSWORD");
1466
                    CREATEWIDGETV(mCurrentDialog, ChangePasswordDialog,
1467
                        loginData);
1468
                    mCurrentDialog->setVisible(Visible_true);
1469
                    BLOCK_END("Client::gameExec State::CHANGEPASSWORD")
1470
                    break;
1471
1472
                case State::CHANGEPASSWORD_ATTEMPT:
1473
                    BLOCK_START("Client::gameExec "
1474
                        "State::CHANGEPASSWORD_ATTEMPT")
1475
                    logger->log1("State: CHANGE PASSWORD ATTEMPT");
1476
                    if (loginHandler != nullptr)
1477
                    {
1478
                        loginHandler->changePassword(loginData.password,
1479
                            loginData.newPassword);
1480
                    }
1481
                    BLOCK_END("Client::gameExec State::CHANGEPASSWORD_ATTEMPT")
1482
                    break;
1483
1484
                case State::CHANGEPASSWORD_SUCCESS:
1485
                    BLOCK_START("Client::gameExec "
1486
                        "State::CHANGEPASSWORD_SUCCESS")
1487
                    logger->log1("State: CHANGE PASSWORD SUCCESS");
1488
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1489
                        // TRANSLATORS: password change message header
1490
                        _("Password Change"),
1491
                        // TRANSLATORS: password change message text
1492
                        _("Password changed successfully!"),
1493
                        // TRANSLATORS: ok dialog button
1494
                        _("OK"),
1495
                        DialogType::ERROR,
1496
                        Modal_true,
1497
                        ShowCenter_true,
1498
                        nullptr,
1499
                        260);
1500
                    mCurrentDialog->addActionListener(&accountListener);
1501
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1502
                    loginData.password = loginData.newPassword;
1503
                    loginData.newPassword.clear();
1504
                    BLOCK_END("Client::gameExec State::CHANGEPASSWORD_SUCCESS")
1505
                    break;
1506
1507
                case State::CHANGEEMAIL:
1508
                    logger->log1("State: CHANGE EMAIL");
1509
                    CREATEWIDGETV(mCurrentDialog,
1510
                        ChangeEmailDialog,
1511
                        loginData);
1512
                    mCurrentDialog->setVisible(Visible_true);
1513
                    break;
1514
1515
                case State::CHANGEEMAIL_ATTEMPT:
1516
                    logger->log1("State: CHANGE EMAIL ATTEMPT");
1517
                    if (loginHandler != nullptr)
1518
                        loginHandler->changeEmail(loginData.email);
1519
                    break;
1520
1521
                case State::CHANGEEMAIL_SUCCESS:
1522
                    logger->log1("State: CHANGE EMAIL SUCCESS");
1523
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1524
                        // TRANSLATORS: email change message header
1525
                        _("Email Change"),
1526
                        // TRANSLATORS: email change message text
1527
                        _("Email changed successfully!"),
1528
                        // TRANSLATORS: ok dialog button
1529
                        _("OK"),
1530
                        DialogType::ERROR,
1531
                        Modal_true,
1532
                        ShowCenter_true,
1533
                        nullptr,
1534
                        260);
1535
                    mCurrentDialog->addActionListener(&accountListener);
1536
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1537
                    break;
1538
1539
                case State::SWITCH_SERVER:
1540
                    BLOCK_START("Client::gameExec State::SWITCH_SERVER")
1541
                    logger->log1("State: SWITCH SERVER");
1542
1543
                    if (loginHandler != nullptr)
1544
                        loginHandler->disconnect();
1545
                    if (gameHandler != nullptr)
1546
                    {
1547
                        gameHandler->disconnect();
1548
                        gameHandler->clear();
1549
                    }
1550
                    settings.serverName.clear();
1551
                    settings.login.clear();
1552
                    WindowManager::updateTitle();
1553
                    serverConfig.write();
1554
                    serverConfig.unload();
1555
                    if (setupWindow != nullptr)
1556
                        setupWindow->externalUnload();
1557
1558
                    mState = State::CHOOSE_SERVER;
1559
                    BLOCK_END("Client::gameExec State::SWITCH_SERVER")
1560
                    break;
1561
1562
                case State::SWITCH_LOGIN:
1563
                    BLOCK_START("Client::gameExec State::SWITCH_LOGIN")
1564
                    logger->log1("State: SWITCH LOGIN");
1565
1566
                    if (loginHandler != nullptr)
1567
                    {
1568
                        loginHandler->logout();
1569
                        loginHandler->disconnect();
1570
                    }
1571
                    if (gameHandler != nullptr)
1572
                        gameHandler->disconnect();
1573
                    if (loginHandler != nullptr)
1574
                        loginHandler->connect();
1575
1576
                    settings.login.clear();
1577
                    WindowManager::updateTitle();
1578
                    mState = State::LOGIN;
1579
                    BLOCK_END("Client::gameExec State::SWITCH_LOGIN")
1580
                    break;
1581
1582
                case State::SWITCH_CHARACTER:
1583
                    BLOCK_START("Client::gameExec State::SWITCH_CHARACTER")
1584
                    logger->log1("State: SWITCH CHARACTER");
1585
1586
                    // Done with game
1587
                    if (gameHandler != nullptr)
1588
                        gameHandler->disconnect();
1589
1590
                    settings.login.clear();
1591
                    WindowManager::updateTitle();
1592
                    mState = State::GET_CHARACTERS;
1593
                    BLOCK_END("Client::gameExec State::SWITCH_CHARACTER")
1594
                    break;
1595
1596
                case State::LOGOUT_ATTEMPT:
1597
                    logger->log1("State: LOGOUT ATTEMPT");
1598
                    break;
1599
1600
                case State::WAIT:
1601
                    logger->log1("State: WAIT");
1602
                    break;
1603
1604
                case State::EXIT:
1605
                    BLOCK_START("Client::gameExec State::EXIT")
1606
                    logger->log1("State: EXIT");
1607
                    Net::unload();
1608
                    BLOCK_END("Client::gameExec State::EXIT")
1609
                    break;
1610
1611
                case State::FORCE_QUIT:
1612
                    BLOCK_START("Client::gameExec State::FORCE_QUIT")
1613
                    logger->log1("State: FORCE QUIT");
1614
                    if (generalHandler != nullptr)
1615
                        generalHandler->unload();
1616
                    mState = State::EXIT;
1617
                    BLOCK_END("Client::gameExec State::FORCE_QUIT")
1618
                  break;
1619
1620
                case State::ERROR:
1621
                    BLOCK_START("Client::gameExec State::ERROR")
1622
                    config.write();
1623
                    if (mOldState == State::GAME)
1624
                        serverConfig.write();
1625
                    logger->log1("State: ERROR");
1626
                    logger->log("Error: %s\n", errorMessage.c_str());
1627
                    pincodeManager.closeDialogs();
1628
                    mCurrentDialog = DialogsManager::openErrorDialog(
1629
                        // TRANSLATORS: error message header
1630
                        _("Error"),
1631
                        errorMessage,
1632
                        Modal_true);
1633
                    mCurrentDialog->addActionListener(&errorListener);
1634
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1635
                    gameHandler->disconnect();
1636
                    BLOCK_END("Client::gameExec State::ERROR")
1637
                    break;
1638
1639
                case State::AUTORECONNECT_SERVER:
1640
                    // ++++++
1641
                    break;
1642
1643
                case State::START:
1644
                default:
1645
                    mState = State::FORCE_QUIT;
1646
                    break;
1647
            }
1648
            BLOCK_END("Client::gameExec 8")
1649
        }
1650
        PROFILER_END();
1651
    }
1652
1653
    return 0;
1654
}
1655
1656
void Client::optionChanged(const std::string &name)
1657
{
1658
    if (name == "fpslimit")
1659
    {
1660
        const int fpsLimit = config.getIntValue("fpslimit");
1661
        settings.limitFps = fpsLimit > 0;
1662
        WindowManager::setFramerate(fpsLimit);
1663
    }
1664
    else if (name == "guialpha" ||
1665
             name == "enableGuiOpacity")
1666
    {
1667
        const float alpha = config.getFloatValue("guialpha");
1668
        settings.guiAlpha = alpha;
1669
        ImageHelper::setEnableAlpha(alpha != 1.0F &&
1670
            config.getBoolValue("enableGuiOpacity"));
1671
    }
1672
    else if (name == "gamma" ||
1673
             name == "enableGamma")
1674
    {
1675
        WindowManager::applyGamma();
1676
    }
1677
    else if (name == "particleEmitterSkip")
1678
    {
1679
        ParticleEngine::emitterSkip =
1680
            config.getIntValue("particleEmitterSkip") + 1;
1681
    }
1682
    else if (name == "vsync")
1683
    {
1684
        WindowManager::applyVSync();
1685
    }
1686
    else if (name == "repeateInterval" ||
1687
             name == "repeateDelay")
1688
    {
1689
        WindowManager::applyKeyRepeat();
1690
    }
1691
}
1692
1693
void Client::action(const ActionEvent &event)
1694
{
1695
    std::string tab;
1696
    const std::string &eventId = event.getId();
1697
1698
    if (eventId == "close")
1699
    {
1700
        setState(State::FORCE_QUIT);
1701
        return;
1702
    }
1703
    if (eventId == "Setup")
1704
    {
1705
        tab.clear();
1706
    }
1707
    else if (eventId == "help")
1708
    {
1709
        inputManager.executeAction(InputAction::WINDOW_HELP);
1710
        return;
1711
    }
1712
    else if (eventId == "about")
1713
    {
1714
        inputManager.executeAction(InputAction::WINDOW_ABOUT);
1715
        return;
1716
    }
1717
    else if (eventId == "Video")
1718
    {
1719
        tab = "Video";
1720
    }
1721
    else if (eventId == "Themes")
1722
    {
1723
        tab = "Theme";
1724
    }
1725
    else if (eventId == "Perfomance")
1726
    {
1727
        tab = "Perfomance";
1728
    }
1729
    else
1730
    {
1731
        return;
1732
    }
1733
1734
    if (setupWindow != nullptr)
1735
    {
1736
        setupWindow->setVisible(fromBool(
1737
            !setupWindow->isWindowVisible(), Visible));
1738
        if (setupWindow->isWindowVisible())
1739
        {
1740
            if (!tab.empty())
1741
                setupWindow->activateTab(tab);
1742
            setupWindow->requestMoveToTop();
1743
        }
1744
    }
1745
}
1746
1747
void Client::initFeatures()
1748
{
1749
    features.init(paths.getStringValue("featuresFile"),
1750
        UseVirtFs_true,
1751
        SkipError_true);
1752
    setFeaturesDefaults(features);
1753
    settings.fixDeadAnimation = features.getBoolValue("fixDeadAnimation");
1754
}
1755
1756
void Client::initPaths()
1757
{
1758
    settings.gmCommandSymbol = paths.getStringValue("gmCommandSymbol");
1759
    settings.gmCharCommandSymbol = paths.getStringValue("gmCharCommandSymbol");
1760
    settings.linkCommandSymbol = paths.getStringValue("linkCommandSymbol");
1761
    if (settings.linkCommandSymbol.empty())
1762
        settings.linkCommandSymbol = "=";
1763
    settings.overweightPercent = paths.getIntValue("overweightPercent");
1764
    settings.playerNameOffset = paths.getIntValue(
1765
        "playerNameOffset");
1766
    settings.playerBadgeAtRightOffset = paths.getIntValue(
1767
        "playerBadgeAtRightOffset");
1768
    settings.unknownSkillsAutoTab = paths.getBoolValue("unknownSkillsAutoTab");
1769
    settings.enableNewMailSystem = paths.getBoolValue("enableNewMailSystem");
1770
}
1771
1772
void Client::initTradeFilter()
1773
{
1774
    const std::string tradeListName =
1775
        settings.serverConfigDir + "/tradefilter.txt";
1776
1777
    std::ofstream tradeFile;
1778
    struct stat statbuf;
1779
1780
    if ((stat(tradeListName.c_str(), &statbuf) != 0) ||
1781
        !S_ISREG(statbuf.st_mode))
1782
    {
1783
        tradeFile.open(tradeListName.c_str(),
1784
            std::ios::out);
1785
        if (tradeFile.is_open())
1786
        {
1787
            tradeFile << ": sell" << std::endl;
1788
            tradeFile << ": buy" << std::endl;
1789
            tradeFile << ": trade" << std::endl;
1790
            tradeFile << "i sell" << std::endl;
1791
            tradeFile << "i buy" << std::endl;
1792
            tradeFile << "i trade" << std::endl;
1793
            tradeFile << "i trading" << std::endl;
1794
            tradeFile << "i am buy" << std::endl;
1795
            tradeFile << "i am sell" << std::endl;
1796
            tradeFile << "i am trade" << std::endl;
1797
            tradeFile << "i am trading" << std::endl;
1798
            tradeFile << "i'm buy" << std::endl;
1799
            tradeFile << "i'm sell" << std::endl;
1800
            tradeFile << "i'm trade" << std::endl;
1801
            tradeFile << "i'm trading" << std::endl;
1802
        }
1803
        else
1804
        {
1805
            reportAlways("Error opening file for writing: %s",
1806
                tradeListName.c_str());
1807
        }
1808
        tradeFile.close();
1809
    }
1810
}
1811
1812
154
bool Client::isTmw()
1813
{
1814
154
    const std::string &name = settings.serverName;
1815
308
    if (name == "server.themanaworld.org" ||
1816

462
        name == "themanaworld.org" ||
1817
154
        name == "167.114.129.72")
1818
    {
1819
        return true;
1820
    }
1821
    return false;
1822
}
1823
1824
void Client::moveButtons(const int width)
1825
{
1826
    if (mSetupButton != nullptr)
1827
    {
1828
        int x = width - mSetupButton->getWidth() - mButtonPadding;
1829
        mSetupButton->setPosition(x, mButtonPadding);
1830
#ifndef WIN32
1831
        x -= mPerfomanceButton->getWidth() + mButtonSpacing;
1832
        mPerfomanceButton->setPosition(x, mButtonPadding);
1833
1834
        x -= mVideoButton->getWidth() + mButtonSpacing;
1835
        mVideoButton->setPosition(x, mButtonPadding);
1836
1837
        x -= mThemesButton->getWidth() + mButtonSpacing;
1838
        mThemesButton->setPosition(x, mButtonPadding);
1839
1840
        x -= mAboutButton->getWidth() + mButtonSpacing;
1841
        mAboutButton->setPosition(x, mButtonPadding);
1842
1843
        x -= mHelpButton->getWidth() + mButtonSpacing;
1844
        mHelpButton->setPosition(x, mButtonPadding);
1845
#ifdef ANDROID
1846
        x -= mCloseButton->getWidth() + mButtonSpacing;
1847
        mCloseButton->setPosition(x, mButtonPadding);
1848
#endif  // ANDROID
1849
#endif  // WIN32
1850
    }
1851
}
1852
1853
126
void Client::windowRemoved(const Window *const window)
1854
{
1855
126
    if (mCurrentDialog == window)
1856
        mCurrentDialog = nullptr;
1857
126
}
1858
1859
void Client::focusWindow()
1860
{
1861
    if (mCurrentDialog != nullptr)
1862
    {
1863
        mCurrentDialog->requestFocus();
1864
    }
1865
}
1866
1867
void Client::updatePinState()
1868
{
1869
    if (mCurrentDialog == nullptr ||
1870
        mState != State::CHAR_SELECT)
1871
    {
1872
        return;
1873
    }
1874
    CharSelectDialog *const dialog =
1875
        dynamic_cast<CharSelectDialog*>(mCurrentDialog);
1876
    if (dialog != nullptr)
1877
        pincodeManager.updateState();
1878
}
1879
1880
void Client::logVars()
1881
{
1882
#ifdef ANDROID
1883
    logger->log("APPDIR: %s", getenv("APPDIR"));
1884
    logger->log("DATADIR2: %s", getSdStoragePath().c_str());
1885
#endif  // ANDROID
1886
}
1887
1888
void Client::slowLogic()
1889
{
1890
    if ((gameHandler == nullptr) ||
1891
        !gameHandler->mustPing())
1892
    {
1893
        return;
1894
    }
1895
1896
    if (get_elapsed_time1(mPing) > 1500)
1897
    {
1898
        mPing = tick_time;
1899
        if (mState == State::UPDATE ||
1900
            mState == State::LOGIN ||
1901
            mState == State::LOGIN_ATTEMPT ||
1902
            mState == State::REGISTER ||
1903
            mState == State::REGISTER_ATTEMPT)
1904
        {
1905
            if (loginHandler != nullptr)
1906
                loginHandler->ping();
1907
            if (generalHandler != nullptr)
1908
                generalHandler->flushSend();
1909
        }
1910
        else if (mState == State::CHAR_SELECT)
1911
        {
1912
            if (charServerHandler != nullptr)
1913
                charServerHandler->ping();
1914
            if (generalHandler != nullptr)
1915
                generalHandler->flushSend();
1916
        }
1917
    }
1918
}
1919
1920
void Client::loadData()
1921
{
1922
    // If another data path has been set,
1923
    // we don't load any other files...
1924
    if (settings.options.dataPath.empty())
1925
    {
1926
        // Add customdata directory
1927
        VirtFs::searchAndAddArchives(
1928
            "customdata/",
1929
            "zip",
1930
            Append_false);
1931
    }
1932
1933
    if (!settings.options.skipUpdate)
1934
    {
1935
        VirtFs::searchAndAddArchives(
1936
            settings.updatesDir + "/local/",
1937
            "zip",
1938
            Append_false);
1939
1940
        VirtFs::mountDir(pathJoin(
1941
            settings.localDataDir,
1942
            settings.updatesDir,
1943
            "local/"),
1944
            Append_false);
1945
    }
1946
1947
    logger->log("Init paths");
1948
    paths.init("paths.xml",
1949
        UseVirtFs_true,
1950
        SkipError_false);
1951
    setPathsDefaults(paths);
1952
    initPaths();
1953
    if (SpriteReference::Empty == nullptr)
1954
    {
1955
        SpriteReference::Empty = new SpriteReference(
1956
            paths.getStringValue("spriteErrorFile"),
1957
            0);
1958
    }
1959
1960
    if (BeingInfo::unknown == nullptr)
1961
        BeingInfo::unknown = new BeingInfo;
1962
1963
    initFeatures();
1964
    TranslationManager::loadCurrentLang();
1965
    TranslationManager::loadDictionaryLang();
1966
    PlayerInfo::stateChange(mState);
1967
1968
    AttributesEnum::init();
1969
    DbManager::loadDb();
1970
1971
    delete spellManager;
1972
    spellManager = new SpellManager;
1973
    delete spellShortcut;
1974
    spellShortcut = new SpellShortcut;
1975
1976
    EquipmentWindow::prepareSlotNames();
1977
1978
    ActorSprite::load();
1979
1980
    if (desktop != nullptr)
1981
        desktop->reloadWallpaper();
1982
}
1983
1984
void Client::unloadData()
1985
{
1986
    DbManager::unloadDb();
1987
    mCurrentServer.supportUrl.clear();
1988
    settings.supportUrl.clear();
1989
    if (settings.options.dataPath.empty())
1990
    {
1991
        // Add customdata directory
1992
        VirtFs::searchAndRemoveArchives(
1993
            "customdata/",
1994
            "zip");
1995
    }
1996
1997
    if (!settings.oldUpdates.empty())
1998
    {
1999
        UpdaterWindow::unloadUpdates(settings.oldUpdates);
2000
        settings.oldUpdates.clear();
2001
    }
2002
2003
    if (!settings.options.skipUpdate)
2004
    {
2005
        VirtFs::searchAndRemoveArchives(
2006
            pathJoin(settings.updatesDir, "local/"),
2007
            "zip");
2008
2009
        VirtFs::unmountDirSilent(pathJoin(
2010
            settings.localDataDir,
2011
            settings.updatesDir,
2012
            "local/"));
2013
    }
2014
2015
    ResourceManager::clearCache();
2016
2017
    loginData.clearUpdateHost();
2018
    localClan.clear();
2019
    serverVersion = 0;
2020
    packetVersion = 0;
2021
    packetVersionMain = 0;
2022
    packetVersionRe = 0;
2023
    packetVersionZero = 0;
2024
    tmwServerVersion = 0;
2025
    evolPacketOffset = 0;
2026
}
2027
2028
void Client::runValidate()
2029
{
2030
    loadData();
2031
    WindowManager::createValidateWindows();
2032
2033
    WindowManager::deleteValidateWindows();
2034
    unloadData();
2035
    delete2(client);
2036
    VirtFs::deinit();
2037
    exit(0);
2038

6
}