GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/progs/manaplus/client.cpp Lines: 97 884 11.0 %
Date: 2018-06-18 21:15:20 Branches: 42 1027 4.1 %

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

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

6
}