GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/progs/manaplus/client.cpp Lines: 98 906 10.8 %
Date: 2018-11-12 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 (loginHandler != nullptr &&
884
             loginHandler->isConnected())
885
    {
886
        mState = State::PRE_LOGIN;
887
    }
888
}
889
890
void Client::stateWorldSelect1()
891
{
892
    if (mOldState == State::UPDATE &&
893
        (loginHandler != nullptr))
894
    {
895
        if (loginHandler->getWorlds().size() < 2)
896
            mState = State::PRE_LOGIN;
897
    }
898
}
899
900
void Client::stateGame1()
901
{
902
    if (gui == nullptr)
903
        return;
904
905
    BasicContainer2 *const top = static_cast<BasicContainer2*>(
906
        gui->getTop());
907
908
    if (top == nullptr)
909
        return;
910
911
    CREATEWIDGETV(desktop, Desktop, nullptr);
912
    top->add(desktop);
913
    int x = top->getWidth() - mButtonPadding;
914
    ADDBUTTON(mSetupButton, new Button(desktop,
915
        // TRANSLATORS: setup tab quick button
916
        _("Setup"), "Setup", BUTTON_SKIN, this))
917
    ADDBUTTON(mPerfomanceButton, new Button(desktop,
918
        // TRANSLATORS: perfoamance tab quick button
919
        _("Performance"), "Perfomance", BUTTON_SKIN, this))
920
    ADDBUTTON(mVideoButton, new Button(desktop,
921
        // TRANSLATORS: video tab quick button
922
        _("Video"), "Video", BUTTON_SKIN, this))
923
    ADDBUTTON(mThemesButton, new Button(desktop,
924
        // TRANSLATORS: theme tab quick button
925
        _("Theme"), "Themes", BUTTON_SKIN, this))
926
    ADDBUTTON(mAboutButton, new Button(desktop,
927
        // TRANSLATORS: theme tab quick button
928
        _("About"), "about", BUTTON_SKIN, this))
929
    ADDBUTTON(mHelpButton, new Button(desktop,
930
        // TRANSLATORS: theme tab quick button
931
        _("Help"), "help", BUTTON_SKIN, this))
932
#ifdef ANDROID
933
    ADDBUTTON(mCloseButton, new Button(desktop,
934
        // TRANSLATORS: close quick button
935
        _("Close"), "close", BUTTON_SKIN, this))
936
#endif  // ANDROID
937
938
    desktop->setSize(mainGraphics->getWidth(),
939
        mainGraphics->getHeight());
940
}
941
942
void Client::stateSwitchLogin1()
943
{
944
    if (mOldState == State::GAME &&
945
        (gameHandler != nullptr))
946
    {
947
        gameHandler->disconnect();
948
    }
949
}
950
951
int Client::gameExec()
952
{
953
    int lastTickTime = tick_time;
954
955
    Perf::init();
956
957
    while (mState != State::EXIT)
958
    {
959
        PROFILER_START();
960
        PERF_STAT(0);
961
        if (eventsManager.handleEvents())
962
            continue;
963
964
        PERF_STAT(1);
965
966
        BLOCK_START("Client::gameExec 3")
967
        if (generalHandler != nullptr)
968
            generalHandler->flushNetwork();
969
        BLOCK_END("Client::gameExec 3")
970
971
        PERF_STAT(2);
972
973
        BLOCK_START("Client::gameExec 4")
974
        if (gui != nullptr)
975
            gui->logic();
976
977
        PERF_STAT(3);
978
979
        cur_time = time(nullptr);
980
        int k = 0;
981
        while (lastTickTime != tick_time &&
982
               k < 40)
983
        {
984
            if (mGame != nullptr)
985
                mGame->logic();
986
            else if (gui != nullptr)
987
                gui->handleInput();
988
989
            ++lastTickTime;
990
            k ++;
991
        }
992
993
        PERF_STAT(4);
994
995
        soundManager.logic();
996
997
        PERF_STAT(5);
998
999
        logic_count += k;
1000
        if (gui != nullptr)
1001
            gui->slowLogic();
1002
1003
        PERF_STAT(6);
1004
1005
        if (mGame != nullptr)
1006
            mGame->slowLogic();
1007
1008
        PERF_STAT(7);
1009
1010
        slowLogic();
1011
1012
        PERF_STAT(8);
1013
1014
        BLOCK_END("Client::gameExec 4")
1015
1016
        // This is done because at some point tick_time will wrap.
1017
        lastTickTime = tick_time;
1018
1019
        BLOCK_START("Client::gameExec 6")
1020
        if (mState == State::CONNECT_GAME)
1021
        {
1022
            stateConnectGame1();
1023
        }
1024
        else if (mState == State::CONNECT_SERVER)
1025
        {
1026
            stateConnectServer1();
1027
        }
1028
        else if (mState == State::WORLD_SELECT)
1029
        {
1030
            stateWorldSelect1();
1031
        }
1032
        else if (mOldState == State::START ||
1033
                 (mOldState == State::GAME && mState != State::GAME))
1034
        {
1035
            stateGame1();
1036
        }
1037
        else if (mState == State::SWITCH_LOGIN)
1038
        {
1039
            stateSwitchLogin1();
1040
        }
1041
        BLOCK_END("Client::gameExec 6")
1042
1043
        PERF_STAT(9);
1044
1045
        if (mState != mOldState)
1046
        {
1047
            BLOCK_START("Client::gameExec 7")
1048
            PlayerInfo::stateChange(mState);
1049
1050
            if (mOldState == State::GAME)
1051
            {
1052
                delete2(mGame);
1053
                assertListener = new AssertListener;
1054
                Game::clearInstance();
1055
                ResourceManager::cleanOrphans(false);
1056
                Party::clearParties();
1057
                Guild::clearGuilds();
1058
                NpcDialog::clearDialogs();
1059
                if (guildHandler != nullptr)
1060
                    guildHandler->clear();
1061
                if (partyHandler != nullptr)
1062
                    partyHandler->clear();
1063
                if (chatLogger != nullptr)
1064
                    chatLogger->clear();
1065
                if (!settings.options.dataPath.empty())
1066
                    UpdaterWindow::unloadMods(settings.options.dataPath);
1067
                else
1068
                    UpdaterWindow::unloadMods(settings.oldUpdates);
1069
                if (!settings.options.skipUpdate)
1070
                    UpdaterWindow::unloadMods(settings.oldUpdates + "/fix/");
1071
            }
1072
            else if (mOldState == State::CHAR_SELECT)
1073
            {
1074
                if (mState != State::CHANGEPASSWORD &&
1075
                    charServerHandler != nullptr)
1076
                {
1077
                    charServerHandler->clear();
1078
                }
1079
            }
1080
1081
            mOldState = mState;
1082
1083
            // Get rid of the dialog of the previous state
1084
            delete2(mCurrentDialog);
1085
1086
            // State has changed, while the quitDialog was active, it might
1087
            // not be correct anymore
1088
            if (mQuitDialog != nullptr)
1089
            {
1090
                mQuitDialog->scheduleDelete();
1091
                mQuitDialog = nullptr;
1092
            }
1093
            BLOCK_END("Client::gameExec 7")
1094
1095
            BLOCK_START("Client::gameExec 8")
1096
            switch (mState)
1097
            {
1098
                case State::CHOOSE_SERVER:
1099
                {
1100
                    BLOCK_START("Client::gameExec STATE_CHOOSE_SERVER")
1101
                    logger->log1("State: CHOOSE SERVER");
1102
                    unloadData();
1103
                    pincodeManager.closeDialogs();
1104
1105
                    // Allow changing this using a server choice dialog
1106
                    // We show the dialog box only if the command-line
1107
                    // options weren't set.
1108
                    if (settings.options.serverName.empty() &&
1109
                        settings.options.serverPort == 0 &&
1110
                        !branding.getValue("onlineServerList", "a").empty())
1111
                    {
1112
                        // Don't allow an alpha opacity
1113
                        // lower than the default value
1114
                        theme->setMinimumOpacity(0.8F);
1115
1116
                        CREATEWIDGETV(mCurrentDialog, ServerDialog,
1117
                            &mCurrentServer,
1118
                            settings.configDir);
1119
                    }
1120
                    else
1121
                    {
1122
                        mState = State::CONNECT_SERVER;
1123
1124
                        // Reset options so that cancelling or connect
1125
                        // timeout will show the server dialog.
1126
                        settings.options.serverName.clear();
1127
                        settings.options.serverPort = 0;
1128
                    }
1129
                    BLOCK_END("Client::gameExec STATE_CHOOSE_SERVER")
1130
                    break;
1131
                }
1132
1133
                case State::CONNECT_SERVER:
1134
                    BLOCK_START("Client::gameExec State::CONNECT_SERVER")
1135
                    logger->log1("State: CONNECT SERVER");
1136
                    loginData.updateHosts.clear();
1137
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1138
                        // TRANSLATORS: connection dialog header
1139
                        _("Connecting to server"),
1140
                        State::SWITCH_SERVER);
1141
                    TranslationManager::loadCurrentLang();
1142
                    TranslationManager::loadDictionaryLang();
1143
                    pincodeManager.init();
1144
                    BLOCK_END("Client::gameExec State::CONNECT_SERVER")
1145
                    break;
1146
1147
                case State::PRE_LOGIN:
1148
                    logger->log1("State: PRE_LOGIN");
1149
                    break;
1150
1151
                case State::LOGIN:
1152
                    BLOCK_START("Client::gameExec State::LOGIN")
1153
                    logger->log1("State: LOGIN");
1154
                    // Don't allow an alpha opacity
1155
                    // lower than the default value
1156
                    theme->setMinimumOpacity(0.8F);
1157
1158
                    if (packetVersion == 0)
1159
                    {
1160
                        packetVersion = loginData.packetVersion;
1161
                        if (packetVersion != 0)
1162
                        {
1163
                            loginHandler->updatePacketVersion();
1164
                            logger->log("Preconfigured packet version: %d",
1165
                                packetVersion);
1166
                        }
1167
                    }
1168
1169
                    loginData.updateType = static_cast<UpdateTypeT>(
1170
                        serverConfig.getValue("updateType", 0));
1171
1172
                    mSearchHash = Net::Download::adlerBuffer(
1173
                        const_cast<char*>(mCurrentServer.hostname.c_str()),
1174
                        CAST_S32(mCurrentServer.hostname.size()));
1175
                    if (settings.options.username.empty() ||
1176
                        settings.options.password.empty())
1177
                    {
1178
                        CREATEWIDGETV(mCurrentDialog, LoginDialog,
1179
                            loginData,
1180
                            &mCurrentServer,
1181
                            &settings.options.updateHost);
1182
                    }
1183
                    else
1184
                    {
1185
                        mState = State::LOGIN_ATTEMPT;
1186
                        // Clear the password so that when login fails, the
1187
                        // dialog will show up next time.
1188
                        settings.options.password.clear();
1189
                    }
1190
                    BLOCK_END("Client::gameExec State::LOGIN")
1191
                    break;
1192
1193
                case State::LOGIN_ATTEMPT:
1194
                    BLOCK_START("Client::gameExec State::LOGIN_ATTEMPT")
1195
                    logger->log1("State: LOGIN ATTEMPT");
1196
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1197
                        // TRANSLATORS: connection dialog header
1198
                        _("Logging in"),
1199
                        State::SWITCH_SERVER);
1200
                    if (loginHandler != nullptr)
1201
                        loginHandler->loginOrRegister(&loginData);
1202
                    BLOCK_END("Client::gameExec State::LOGIN_ATTEMPT")
1203
                    break;
1204
1205
                case State::WORLD_SELECT:
1206
                    BLOCK_START("Client::gameExec State::WORLD_SELECT")
1207
                    logger->log1("State: WORLD SELECT");
1208
                    {
1209
                        TranslationManager::loadCurrentLang();
1210
                        TranslationManager::loadDictionaryLang();
1211
                        if (loginHandler == nullptr)
1212
                        {
1213
                            BLOCK_END("Client::gameExec State::WORLD_SELECT")
1214
                            break;
1215
                        }
1216
                        Worlds worlds = loginHandler->getWorlds();
1217
1218
                        if (worlds.empty())
1219
                        {
1220
                            // Trust that the netcode knows what it's doing
1221
                            mState = State::UPDATE;
1222
                        }
1223
                        else if (worlds.size() == 1)
1224
                        {
1225
                            loginHandler->chooseServer(
1226
                                0, mCurrentServer.persistentIp);
1227
                            mState = State::UPDATE;
1228
                        }
1229
                        else
1230
                        {
1231
                            CREATEWIDGETV(mCurrentDialog, WorldSelectDialog,
1232
                                worlds);
1233
                            if (settings.options.chooseDefault)
1234
                            {
1235
                                static_cast<WorldSelectDialog*>(mCurrentDialog)
1236
                                    ->action(ActionEvent(nullptr, "ok"));
1237
                            }
1238
                        }
1239
                    }
1240
                    BLOCK_END("Client::gameExec State::WORLD_SELECT")
1241
                    break;
1242
1243
                case State::WORLD_SELECT_ATTEMPT:
1244
                    BLOCK_START("Client::gameExec State::WORLD_SELECT_ATTEMPT")
1245
                    logger->log1("State: WORLD SELECT ATTEMPT");
1246
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1247
                        // TRANSLATORS: connection dialog header
1248
                        _("Entering game world"),
1249
                        State::WORLD_SELECT);
1250
                    BLOCK_END("Client::gameExec State::WORLD_SELECT_ATTEMPT")
1251
                    break;
1252
1253
                case State::UPDATE:
1254
                    BLOCK_START("Client::gameExec State::UPDATE")
1255
                    logger->log1("State: UPDATE");
1256
1257
                    // Determine which source to use for the update host
1258
                    if (!settings.options.updateHost.empty())
1259
                        settings.updateHost = settings.options.updateHost;
1260
                    else
1261
                        settings.updateHost = loginData.updateHost;
1262
                    Dirs::initUpdatesDir();
1263
1264
                    if (!settings.oldUpdates.empty())
1265
                        UpdaterWindow::unloadUpdates(settings.oldUpdates);
1266
1267
                    if (settings.options.skipUpdate)
1268
                    {
1269
                        mState = State::LOAD_DATA;
1270
                        settings.oldUpdates.clear();
1271
                        UpdaterWindow::loadDirMods(settings.options.dataPath);
1272
                    }
1273
                    else if ((loginData.updateType & UpdateType::Skip) != 0)
1274
                    {
1275
                        settings.oldUpdates = pathJoin(settings.localDataDir,
1276
                            settings.updatesDir);
1277
                        UpdaterWindow::loadLocalUpdates(settings.oldUpdates);
1278
                        mState = State::LOAD_DATA;
1279
                    }
1280
                    else
1281
                    {
1282
                        settings.oldUpdates = pathJoin(settings.localDataDir,
1283
                            settings.updatesDir);
1284
                        CREATEWIDGETV(mCurrentDialog, UpdaterWindow,
1285
                            settings.updateHost,
1286
                            settings.oldUpdates,
1287
                            settings.options.dataPath.empty(),
1288
                            loginData.updateType);
1289
                    }
1290
                    BLOCK_END("Client::gameExec State::UPDATE")
1291
                    break;
1292
1293
                case State::LOAD_DATA:
1294
                {
1295
                    BLOCK_START("Client::gameExec State::LOAD_DATA")
1296
                    logger->log1("State: LOAD DATA");
1297
1298
                    loadData();
1299
1300
                    mState = State::GET_CHARACTERS;
1301
                    BLOCK_END("Client::gameExec State::LOAD_DATA")
1302
                    break;
1303
                }
1304
                case State::GET_CHARACTERS:
1305
                    BLOCK_START("Client::gameExec State::GET_CHARACTERS")
1306
                    logger->log1("State: GET CHARACTERS");
1307
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1308
                        // TRANSLATORS: connection dialog header
1309
                        _("Requesting characters"),
1310
                        State::SWITCH_SERVER);
1311
                    if (charServerHandler != nullptr)
1312
                        charServerHandler->requestCharacters();
1313
                    BLOCK_END("Client::gameExec State::GET_CHARACTERS")
1314
                    break;
1315
1316
                case State::CHAR_SELECT:
1317
                    BLOCK_START("Client::gameExec State::CHAR_SELECT")
1318
                    logger->log1("State: CHAR SELECT");
1319
                    // Don't allow an alpha opacity
1320
                    // lower than the default value
1321
                    theme->setMinimumOpacity(0.8F);
1322
1323
                    settings.login = loginData.username;
1324
                    WindowManager::updateTitle();
1325
1326
                    CREATEWIDGETV(mCurrentDialog, CharSelectDialog,
1327
                        loginData);
1328
                    pincodeManager.updateState();
1329
1330
                    if (!(static_cast<CharSelectDialog*>(mCurrentDialog))
1331
                        ->selectByName(settings.options.character,
1332
                        CharSelectDialog::Choose))
1333
                    {
1334
                        (static_cast<CharSelectDialog*>(mCurrentDialog))
1335
                            ->selectByName(
1336
                            serverConfig.getValue("lastCharacter", ""),
1337
                            settings.options.chooseDefault ?
1338
                            CharSelectDialog::Choose :
1339
                            CharSelectDialog::Focus);
1340
                    }
1341
1342
                    // Choosing character on the command line should work only
1343
                    // once, clear it so that 'switch character' works.
1344
                    settings.options.character.clear();
1345
                    BLOCK_END("Client::gameExec State::CHAR_SELECT")
1346
                    break;
1347
1348
                case State::CONNECT_GAME:
1349
                    BLOCK_START("Client::gameExec State::CONNECT_GAME")
1350
                    logger->log1("State: CONNECT GAME");
1351
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1352
                        // TRANSLATORS: connection dialog header
1353
                        _("Connecting to the game server"),
1354
                        State::CHOOSE_SERVER);
1355
                    if (gameHandler != nullptr)
1356
                        gameHandler->connect();
1357
                    BLOCK_END("Client::gameExec State::CONNECT_GAME")
1358
                    break;
1359
1360
                case State::CHANGE_MAP:
1361
                    BLOCK_START("Client::gameExec State::CHANGE_MAP")
1362
                    logger->log1("State: CHANGE_MAP");
1363
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1364
                        // TRANSLATORS: connection dialog header
1365
                        _("Changing game servers"),
1366
                        State::SWITCH_CHARACTER);
1367
                    if (gameHandler != nullptr)
1368
                        gameHandler->connect();
1369
                    BLOCK_END("Client::gameExec State::CHANGE_MAP")
1370
                    break;
1371
1372
                case State::GAME:
1373
                    BLOCK_START("Client::gameExec State::GAME")
1374
                    if (localPlayer != nullptr)
1375
                    {
1376
                        logger->log("Memorizing selected character %s",
1377
                            localPlayer->getName().c_str());
1378
                        serverConfig.setValue("lastCharacter",
1379
                            localPlayer->getName());
1380
#ifdef USE_MUMBLE
1381
                        if (mumbleManager)
1382
                            mumbleManager->setPlayer(localPlayer->getName());
1383
#endif  // USE_MUMBLE
1384
                        Perf::init();
1385
                    }
1386
1387
                    // Fade out logon-music here too to give the desired effect
1388
                    // of "flowing" into the game.
1389
                    soundManager.fadeOutMusic(1000);
1390
1391
                    // Allow any alpha opacity
1392
                    theme->setMinimumOpacity(-1.0F);
1393
1394
                    if (chatLogger != nullptr)
1395
                        chatLogger->setServerName(settings.serverName);
1396
1397
#ifdef ANDROID
1398
                    delete2(mCloseButton);
1399
#endif  // ANDROID
1400
1401
                    delete2(mSetupButton);
1402
                    delete2(mVideoButton);
1403
                    delete2(mThemesButton);
1404
                    delete2(mAboutButton);
1405
                    delete2(mHelpButton);
1406
                    delete2(mPerfomanceButton);
1407
                    delete2(desktop);
1408
1409
                    mCurrentDialog = nullptr;
1410
1411
                    logger->log1("State: GAME");
1412
                    if (generalHandler != nullptr)
1413
                        generalHandler->reloadPartially();
1414
                    mGame = new Game;
1415
                    BLOCK_END("Client::gameExec State::GAME")
1416
                    break;
1417
1418
                case State::LOGIN_ERROR:
1419
                    BLOCK_START("Client::gameExec State::LOGIN_ERROR")
1420
                    logger->log1("State: LOGIN ERROR");
1421
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1422
                        // TRANSLATORS: error dialog header
1423
                        _("Error"),
1424
                        errorMessage,
1425
                        // TRANSLATORS: ok dialog button
1426
                        _("Close"),
1427
                        DialogType::ERROR,
1428
                        Modal_true,
1429
                        ShowCenter_true,
1430
                        nullptr,
1431
                        260);
1432
                    mCurrentDialog->addActionListener(&loginListener);
1433
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1434
                    BLOCK_END("Client::gameExec State::LOGIN_ERROR")
1435
                    break;
1436
1437
                case State::ACCOUNTCHANGE_ERROR:
1438
                    BLOCK_START("Client::gameExec State::ACCOUNTCHANGE_ERROR")
1439
                    logger->log1("State: ACCOUNT CHANGE ERROR");
1440
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1441
                        // TRANSLATORS: error dialog header
1442
                        _("Error"),
1443
                        errorMessage,
1444
                        // TRANSLATORS: ok dialog button
1445
                        _("Close"),
1446
                        DialogType::ERROR,
1447
                        Modal_true,
1448
                        ShowCenter_true,
1449
                        nullptr,
1450
                        260);
1451
                    mCurrentDialog->addActionListener(&accountListener);
1452
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1453
                    BLOCK_END("Client::gameExec State::ACCOUNTCHANGE_ERROR")
1454
                    break;
1455
1456
                case State::REGISTER_PREP:
1457
                    BLOCK_START("Client::gameExec State::REGISTER_PREP")
1458
                    logger->log1("State: REGISTER_PREP");
1459
                    CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
1460
                        // TRANSLATORS: connection dialog header
1461
                        _("Requesting registration details"),
1462
                        State::LOGIN);
1463
                    loginHandler->getRegistrationDetails();
1464
                    BLOCK_END("Client::gameExec State::REGISTER_PREP")
1465
                    break;
1466
1467
                case State::REGISTER:
1468
                    logger->log1("State: REGISTER");
1469
                    CREATEWIDGETV(mCurrentDialog, RegisterDialog,
1470
                        loginData);
1471
                    break;
1472
1473
                case State::REGISTER_ATTEMPT:
1474
                    BLOCK_START("Client::gameExec State::REGISTER_ATTEMPT")
1475
                    logger->log("Username is %s", loginData.username.c_str());
1476
                    if (loginHandler != nullptr)
1477
                        loginHandler->registerAccount(&loginData);
1478
                    BLOCK_END("Client::gameExec State::REGISTER_ATTEMPT")
1479
                    break;
1480
1481
                case State::CHANGEPASSWORD:
1482
                    BLOCK_START("Client::gameExec State::CHANGEPASSWORD")
1483
                    logger->log1("State: CHANGE PASSWORD");
1484
                    CREATEWIDGETV(mCurrentDialog, ChangePasswordDialog,
1485
                        loginData);
1486
                    mCurrentDialog->setVisible(Visible_true);
1487
                    BLOCK_END("Client::gameExec State::CHANGEPASSWORD")
1488
                    break;
1489
1490
                case State::CHANGEPASSWORD_ATTEMPT:
1491
                    BLOCK_START("Client::gameExec "
1492
                        "State::CHANGEPASSWORD_ATTEMPT")
1493
                    logger->log1("State: CHANGE PASSWORD ATTEMPT");
1494
                    if (loginHandler != nullptr)
1495
                    {
1496
                        loginHandler->changePassword(loginData.password,
1497
                            loginData.newPassword);
1498
                    }
1499
                    BLOCK_END("Client::gameExec State::CHANGEPASSWORD_ATTEMPT")
1500
                    break;
1501
1502
                case State::CHANGEPASSWORD_SUCCESS:
1503
                    BLOCK_START("Client::gameExec "
1504
                        "State::CHANGEPASSWORD_SUCCESS")
1505
                    logger->log1("State: CHANGE PASSWORD SUCCESS");
1506
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1507
                        // TRANSLATORS: password change message header
1508
                        _("Password Change"),
1509
                        // TRANSLATORS: password change message text
1510
                        _("Password changed successfully!"),
1511
                        // TRANSLATORS: ok dialog button
1512
                        _("OK"),
1513
                        DialogType::ERROR,
1514
                        Modal_true,
1515
                        ShowCenter_true,
1516
                        nullptr,
1517
                        260);
1518
                    mCurrentDialog->addActionListener(&accountListener);
1519
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1520
                    loginData.password = loginData.newPassword;
1521
                    loginData.newPassword.clear();
1522
                    BLOCK_END("Client::gameExec State::CHANGEPASSWORD_SUCCESS")
1523
                    break;
1524
1525
                case State::CHANGEEMAIL:
1526
                    logger->log1("State: CHANGE EMAIL");
1527
                    CREATEWIDGETV(mCurrentDialog,
1528
                        ChangeEmailDialog,
1529
                        loginData);
1530
                    mCurrentDialog->setVisible(Visible_true);
1531
                    break;
1532
1533
                case State::CHANGEEMAIL_ATTEMPT:
1534
                    logger->log1("State: CHANGE EMAIL ATTEMPT");
1535
                    if (loginHandler != nullptr)
1536
                        loginHandler->changeEmail(loginData.email);
1537
                    break;
1538
1539
                case State::CHANGEEMAIL_SUCCESS:
1540
                    logger->log1("State: CHANGE EMAIL SUCCESS");
1541
                    CREATEWIDGETV(mCurrentDialog, OkDialog,
1542
                        // TRANSLATORS: email change message header
1543
                        _("Email Change"),
1544
                        // TRANSLATORS: email change message text
1545
                        _("Email changed successfully!"),
1546
                        // TRANSLATORS: ok dialog button
1547
                        _("OK"),
1548
                        DialogType::ERROR,
1549
                        Modal_true,
1550
                        ShowCenter_true,
1551
                        nullptr,
1552
                        260);
1553
                    mCurrentDialog->addActionListener(&accountListener);
1554
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1555
                    break;
1556
1557
                case State::SWITCH_SERVER:
1558
                    BLOCK_START("Client::gameExec State::SWITCH_SERVER")
1559
                    logger->log1("State: SWITCH SERVER");
1560
1561
                    if (loginHandler != nullptr)
1562
                        loginHandler->disconnect();
1563
                    if (gameHandler != nullptr)
1564
                    {
1565
                        gameHandler->disconnect();
1566
                        gameHandler->clear();
1567
                    }
1568
                    settings.serverName.clear();
1569
                    settings.login.clear();
1570
                    WindowManager::updateTitle();
1571
                    serverConfig.write();
1572
                    serverConfig.unload();
1573
                    if (setupWindow != nullptr)
1574
                        setupWindow->externalUnload();
1575
1576
                    mState = State::CHOOSE_SERVER;
1577
                    BLOCK_END("Client::gameExec State::SWITCH_SERVER")
1578
                    break;
1579
1580
                case State::SWITCH_LOGIN:
1581
                    BLOCK_START("Client::gameExec State::SWITCH_LOGIN")
1582
                    logger->log1("State: SWITCH LOGIN");
1583
1584
                    if (loginHandler != nullptr)
1585
                    {
1586
                        loginHandler->logout();
1587
                        loginHandler->disconnect();
1588
                    }
1589
                    if (gameHandler != nullptr)
1590
                        gameHandler->disconnect();
1591
                    if (loginHandler != nullptr)
1592
                        loginHandler->connect();
1593
1594
                    settings.login.clear();
1595
                    WindowManager::updateTitle();
1596
                    mState = State::LOGIN;
1597
                    BLOCK_END("Client::gameExec State::SWITCH_LOGIN")
1598
                    break;
1599
1600
                case State::SWITCH_CHARACTER:
1601
                    BLOCK_START("Client::gameExec State::SWITCH_CHARACTER")
1602
                    logger->log1("State: SWITCH CHARACTER");
1603
1604
                    // Done with game
1605
                    if (gameHandler != nullptr)
1606
                        gameHandler->disconnect();
1607
1608
                    settings.login.clear();
1609
                    WindowManager::updateTitle();
1610
                    mState = State::GET_CHARACTERS;
1611
                    BLOCK_END("Client::gameExec State::SWITCH_CHARACTER")
1612
                    break;
1613
1614
                case State::LOGOUT_ATTEMPT:
1615
                    logger->log1("State: LOGOUT ATTEMPT");
1616
                    break;
1617
1618
                case State::WAIT:
1619
                    logger->log1("State: WAIT");
1620
                    break;
1621
1622
                case State::EXIT:
1623
                    BLOCK_START("Client::gameExec State::EXIT")
1624
                    logger->log1("State: EXIT");
1625
                    Net::unload();
1626
                    BLOCK_END("Client::gameExec State::EXIT")
1627
                    break;
1628
1629
                case State::FORCE_QUIT:
1630
                    BLOCK_START("Client::gameExec State::FORCE_QUIT")
1631
                    logger->log1("State: FORCE QUIT");
1632
                    if (generalHandler != nullptr)
1633
                        generalHandler->unload();
1634
                    mState = State::EXIT;
1635
                    BLOCK_END("Client::gameExec State::FORCE_QUIT")
1636
                  break;
1637
1638
                case State::ERROR:
1639
                    BLOCK_START("Client::gameExec State::ERROR")
1640
                    config.write();
1641
                    if (mOldState == State::GAME)
1642
                        serverConfig.write();
1643
                    logger->log1("State: ERROR");
1644
                    logger->log("Error: %s\n", errorMessage.c_str());
1645
                    pincodeManager.closeDialogs();
1646
                    mCurrentDialog = DialogsManager::openErrorDialog(
1647
                        // TRANSLATORS: error message header
1648
                        _("Error"),
1649
                        errorMessage,
1650
                        Modal_true);
1651
                    mCurrentDialog->addActionListener(&errorListener);
1652
                    mCurrentDialog = nullptr;  // OkDialog deletes itself
1653
                    gameHandler->disconnect();
1654
                    BLOCK_END("Client::gameExec State::ERROR")
1655
                    break;
1656
1657
                case State::AUTORECONNECT_SERVER:
1658
                    // ++++++
1659
                    break;
1660
1661
                case State::START:
1662
                default:
1663
                    mState = State::FORCE_QUIT;
1664
                    break;
1665
            }
1666
            BLOCK_END("Client::gameExec 8")
1667
        }
1668
1669
        PERF_STAT(10);
1670
1671
        // Update the screen when application is visible, delay otherwise.
1672
        if (!WindowManager::getIsMinimized())
1673
        {
1674
            frame_count++;
1675
            if (gui != nullptr)
1676
                gui->draw();
1677
            mainGraphics->updateScreen();
1678
        }
1679
        else
1680
        {
1681
            SDL_Delay(100);
1682
        }
1683
1684
        PERF_STAT(11);
1685
1686
        BLOCK_START("~Client::SDL_framerateDelay")
1687
        if (settings.limitFps)
1688
            SDL_framerateDelay(&fpsManager);
1689
        BLOCK_END("~Client::SDL_framerateDelay")
1690
1691
        PERF_STAT(12);
1692
        PERF_NEXTFRAME();
1693
        PROFILER_END();
1694
    }
1695
1696
    return 0;
1697
}
1698
1699
void Client::optionChanged(const std::string &name)
1700
{
1701
    if (name == "fpslimit")
1702
    {
1703
        const int fpsLimit = config.getIntValue("fpslimit");
1704
        settings.limitFps = fpsLimit > 0;
1705
        WindowManager::setFramerate(fpsLimit);
1706
    }
1707
    else if (name == "guialpha" ||
1708
             name == "enableGuiOpacity")
1709
    {
1710
        const float alpha = config.getFloatValue("guialpha");
1711
        settings.guiAlpha = alpha;
1712
        ImageHelper::setEnableAlpha(alpha != 1.0F &&
1713
            config.getBoolValue("enableGuiOpacity"));
1714
    }
1715
    else if (name == "gamma" ||
1716
             name == "enableGamma")
1717
    {
1718
        WindowManager::applyGamma();
1719
    }
1720
    else if (name == "particleEmitterSkip")
1721
    {
1722
        ParticleEngine::emitterSkip =
1723
            config.getIntValue("particleEmitterSkip") + 1;
1724
    }
1725
    else if (name == "vsync")
1726
    {
1727
        WindowManager::applyVSync();
1728
    }
1729
    else if (name == "repeateInterval" ||
1730
             name == "repeateDelay")
1731
    {
1732
        WindowManager::applyKeyRepeat();
1733
    }
1734
}
1735
1736
void Client::action(const ActionEvent &event)
1737
{
1738
    std::string tab;
1739
    const std::string &eventId = event.getId();
1740
1741
    if (eventId == "close")
1742
    {
1743
        setState(State::FORCE_QUIT);
1744
        return;
1745
    }
1746
    if (eventId == "Setup")
1747
    {
1748
        tab.clear();
1749
    }
1750
    else if (eventId == "help")
1751
    {
1752
        inputManager.executeAction(InputAction::WINDOW_HELP);
1753
        return;
1754
    }
1755
    else if (eventId == "about")
1756
    {
1757
        inputManager.executeAction(InputAction::WINDOW_ABOUT);
1758
        return;
1759
    }
1760
    else if (eventId == "Video")
1761
    {
1762
        tab = "Video";
1763
    }
1764
    else if (eventId == "Themes")
1765
    {
1766
        tab = "Theme";
1767
    }
1768
    else if (eventId == "Perfomance")
1769
    {
1770
        tab = "Perfomance";
1771
    }
1772
    else
1773
    {
1774
        return;
1775
    }
1776
1777
    if (setupWindow != nullptr)
1778
    {
1779
        setupWindow->setVisible(fromBool(
1780
            !setupWindow->isWindowVisible(), Visible));
1781
        if (setupWindow->isWindowVisible())
1782
        {
1783
            if (!tab.empty())
1784
                setupWindow->activateTab(tab);
1785
            setupWindow->requestMoveToTop();
1786
        }
1787
    }
1788
}
1789
1790
void Client::initFeatures()
1791
{
1792
    features.init(paths.getStringValue("featuresFile"),
1793
        UseVirtFs_true,
1794
        SkipError_true);
1795
    setFeaturesDefaults(features);
1796
    settings.fixDeadAnimation = features.getBoolValue("fixDeadAnimation");
1797
}
1798
1799
void Client::initPaths()
1800
{
1801
    settings.gmCommandSymbol = paths.getStringValue("gmCommandSymbol");
1802
    settings.gmCharCommandSymbol = paths.getStringValue("gmCharCommandSymbol");
1803
    settings.linkCommandSymbol = paths.getStringValue("linkCommandSymbol");
1804
    if (settings.linkCommandSymbol.empty())
1805
        settings.linkCommandSymbol = "=";
1806
    settings.overweightPercent = paths.getIntValue("overweightPercent");
1807
    settings.playerNameOffset = paths.getIntValue(
1808
        "playerNameOffset");
1809
    settings.playerBadgeAtRightOffset = paths.getIntValue(
1810
        "playerBadgeAtRightOffset");
1811
    settings.unknownSkillsAutoTab = paths.getBoolValue("unknownSkillsAutoTab");
1812
    settings.enableNewMailSystem = paths.getBoolValue("enableNewMailSystem");
1813
}
1814
1815
void Client::initTradeFilter()
1816
{
1817
    const std::string tradeListName =
1818
        settings.serverConfigDir + "/tradefilter.txt";
1819
1820
    std::ofstream tradeFile;
1821
    struct stat statbuf;
1822
1823
    if ((stat(tradeListName.c_str(), &statbuf) != 0) ||
1824
        !S_ISREG(statbuf.st_mode))
1825
    {
1826
        tradeFile.open(tradeListName.c_str(),
1827
            std::ios::out);
1828
        if (tradeFile.is_open())
1829
        {
1830
            tradeFile << ": sell" << std::endl;
1831
            tradeFile << ": buy" << std::endl;
1832
            tradeFile << ": trade" << std::endl;
1833
            tradeFile << "i sell" << std::endl;
1834
            tradeFile << "i buy" << std::endl;
1835
            tradeFile << "i trade" << std::endl;
1836
            tradeFile << "i trading" << std::endl;
1837
            tradeFile << "i am buy" << std::endl;
1838
            tradeFile << "i am sell" << std::endl;
1839
            tradeFile << "i am trade" << std::endl;
1840
            tradeFile << "i am trading" << std::endl;
1841
            tradeFile << "i'm buy" << std::endl;
1842
            tradeFile << "i'm sell" << std::endl;
1843
            tradeFile << "i'm trade" << std::endl;
1844
            tradeFile << "i'm trading" << std::endl;
1845
        }
1846
        else
1847
        {
1848
            reportAlways("Error opening file for writing: %s",
1849
                tradeListName.c_str());
1850
        }
1851
        tradeFile.close();
1852
    }
1853
}
1854
1855
77
bool Client::isTmw()
1856
{
1857
77
    const std::string &name = settings.serverName;
1858

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

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

3
}