GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/progs/manaplus/client.cpp Lines: 98 906 10.8 %
Date: 2020-06-04 Branches: 43 911 4.7 %

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

231
    if (name == "server.themanaworld.org" ||
1875

231
        name == "themanaworld.org" ||
1876
77
        name == "167.114.129.72")
1877
    {
1878
        return true;
1879
    }
1880
77
    return false;
1881
}
1882
1883
void Client::moveButtons(const int width)
1884
{
1885
    if (mSetupButton != nullptr)
1886
    {
1887
        int x = width - mSetupButton->getWidth() - mButtonPadding;
1888
        mSetupButton->setPosition(x, mButtonPadding);
1889
#ifndef WIN32
1890
        x -= mPerfomanceButton->getWidth() + mButtonSpacing;
1891
        mPerfomanceButton->setPosition(x, mButtonPadding);
1892
1893
        x -= mVideoButton->getWidth() + mButtonSpacing;
1894
        mVideoButton->setPosition(x, mButtonPadding);
1895
1896
        x -= mThemesButton->getWidth() + mButtonSpacing;
1897
        mThemesButton->setPosition(x, mButtonPadding);
1898
1899
        x -= mAboutButton->getWidth() + mButtonSpacing;
1900
        mAboutButton->setPosition(x, mButtonPadding);
1901
1902
        x -= mHelpButton->getWidth() + mButtonSpacing;
1903
        mHelpButton->setPosition(x, mButtonPadding);
1904
#ifdef ANDROID
1905
        x -= mCloseButton->getWidth() + mButtonSpacing;
1906
        mCloseButton->setPosition(x, mButtonPadding);
1907
#endif  // ANDROID
1908
#endif  // WIN32
1909
    }
1910
}
1911
1912
63
void Client::windowRemoved(const Window *const window)
1913
{
1914
63
    if (mCurrentDialog == window)
1915
        mCurrentDialog = nullptr;
1916
63
}
1917
1918
void Client::focusWindow()
1919
{
1920
    if (mCurrentDialog != nullptr)
1921
    {
1922
        mCurrentDialog->requestFocus();
1923
    }
1924
}
1925
1926
void Client::updatePinState()
1927
{
1928
    if (mCurrentDialog == nullptr ||
1929
        mState != State::CHAR_SELECT)
1930
    {
1931
        return;
1932
    }
1933
    CharSelectDialog *const dialog =
1934
        dynamic_cast<CharSelectDialog*>(mCurrentDialog);
1935
    if (dialog != nullptr)
1936
        pincodeManager.updateState();
1937
}
1938
1939
void Client::logVars()
1940
{
1941
#ifdef ANDROID
1942
    logger->log("APPDIR: %s", getenv("APPDIR"));
1943
    logger->log("DATADIR2: %s", getSdStoragePath().c_str());
1944
#endif  // ANDROID
1945
}
1946
1947
void Client::slowLogic()
1948
{
1949
    if ((gameHandler == nullptr) ||
1950
        !gameHandler->mustPing())
1951
    {
1952
        return;
1953
    }
1954
1955
    if (get_elapsed_time1(mPing) > 1500)
1956
    {
1957
        mPing = tick_time;
1958
        if (mState == State::UPDATE ||
1959
            mState == State::LOGIN ||
1960
            mState == State::LOGIN_ATTEMPT ||
1961
            mState == State::REGISTER ||
1962
            mState == State::REGISTER_ATTEMPT)
1963
        {
1964
            if (loginHandler != nullptr)
1965
                loginHandler->ping();
1966
            if (generalHandler != nullptr)
1967
                generalHandler->flushSend();
1968
        }
1969
        else if (mState == State::CHAR_SELECT)
1970
        {
1971
            if (charServerHandler != nullptr)
1972
                charServerHandler->ping();
1973
            if (generalHandler != nullptr)
1974
                generalHandler->flushSend();
1975
        }
1976
    }
1977
}
1978
1979
void Client::loadData()
1980
{
1981
    // If another data path has been set,
1982
    // we don't load any other files...
1983
    if (settings.options.dataPath.empty())
1984
    {
1985
        // Add customdata directory
1986
        VirtFs::searchAndAddArchives(
1987
            "customdata/",
1988
            "zip",
1989
            Append_false);
1990
    }
1991
1992
    if (!settings.options.skipUpdate)
1993
    {
1994
        VirtFs::searchAndAddArchives(
1995
            settings.updatesDir + "/local/",
1996
            "zip",
1997
            Append_false);
1998
1999
        VirtFs::mountDir(pathJoin(
2000
            settings.localDataDir,
2001
            settings.updatesDir,
2002
            "local/"),
2003
            Append_false);
2004
    }
2005
2006
    logger->log("Init paths");
2007
    paths.init("paths.xml",
2008
        UseVirtFs_true,
2009
        SkipError_false);
2010
    setPathsDefaults(paths);
2011
    initPaths();
2012
    if (SpriteReference::Empty == nullptr)
2013
    {
2014
        SpriteReference::Empty = new SpriteReference(
2015
            paths.getStringValue("spriteErrorFile"),
2016
            0);
2017
    }
2018
2019
    if (BeingInfo::unknown == nullptr)
2020
        BeingInfo::unknown = new BeingInfo;
2021
2022
    initFeatures();
2023
    TranslationManager::loadCurrentLang();
2024
    TranslationManager::loadDictionaryLang();
2025
    PlayerInfo::stateChange(mState);
2026
2027
    AttributesEnum::init();
2028
    DbManager::loadDb();
2029
2030
    delete spellManager;
2031
    spellManager = new SpellManager;
2032
    delete spellShortcut;
2033
    spellShortcut = new SpellShortcut;
2034
2035
    EquipmentWindow::prepareSlotNames();
2036
2037
    ActorSprite::load();
2038
2039
    if (desktop != nullptr)
2040
        desktop->reloadWallpaper();
2041
}
2042
2043
void Client::unloadData()
2044
{
2045
    DbManager::unloadDb();
2046
    mCurrentServer.supportUrl.clear();
2047
    settings.supportUrl.clear();
2048
    if (settings.options.dataPath.empty())
2049
    {
2050
        // Add customdata directory
2051
        VirtFs::searchAndRemoveArchives(
2052
            "customdata/",
2053
            "zip");
2054
    }
2055
2056
    if (!settings.oldUpdates.empty())
2057
    {
2058
        UpdaterWindow::unloadUpdates(settings.oldUpdates);
2059
        settings.oldUpdates.clear();
2060
    }
2061
2062
    if (!settings.options.skipUpdate)
2063
    {
2064
        VirtFs::searchAndRemoveArchives(
2065
            pathJoin(settings.updatesDir, "local/"),
2066
            "zip");
2067
2068
        VirtFs::unmountDirSilent(pathJoin(
2069
            settings.localDataDir,
2070
            settings.updatesDir,
2071
            "local/"));
2072
    }
2073
2074
    ResourceManager::clearCache();
2075
2076
    loginData.clearUpdateHost();
2077
    localClan.clear();
2078
    serverVersion = 0;
2079
    packetVersion = 0;
2080
    packetVersionMain = 0;
2081
    packetVersionRe = 0;
2082
    packetVersionZero = 0;
2083
    tmwServerVersion = 0;
2084
    evolPacketOffset = 0;
2085
}
2086
2087
void Client::runValidate()
2088
{
2089
    loadData();
2090
    WindowManager::createValidateWindows();
2091
2092
    WindowManager::deleteValidateWindows();
2093
    unloadData();
2094
    delete2(client)
2095
    VirtFs::deinit();
2096
    exit(0);
2097

3
}