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

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

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

3
}