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

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

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

3
}