GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/game.cpp Lines: 1 578 0.2 %
Date: 2018-05-24 20:39:27 Branches: 2 913 0.2 %

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 "game.h"
24
25
#include "actormanager.h"
26
#include "client.h"
27
#include "configuration.h"
28
#include "effectmanager.h"
29
#include "eventsmanager.h"
30
#include "gamemodifiers.h"
31
#include "soundmanager.h"
32
#include "settings.h"
33
34
#include "being/crazymoves.h"
35
#include "being/localplayer.h"
36
#include "being/playerinfo.h"
37
38
#include "const/itemshortcut.h"
39
#include "const/spells.h"
40
41
#include "const/gui/chat.h"
42
43
#include "enums/being/beingdirection.h"
44
45
#include "fs/mkdir.h"
46
47
#include "fs/virtfs/fs.h"
48
49
#include "gui/dialogsmanager.h"
50
#include "gui/gui.h"
51
#include "gui/popupmanager.h"
52
#include "gui/viewport.h"
53
#include "gui/windowmanager.h"
54
#include "gui/windowmenu.h"
55
56
#include "gui/fonts/font.h"
57
58
#include "gui/shortcut/dropshortcut.h"
59
#include "gui/shortcut/emoteshortcut.h"
60
61
#include "gui/popups/popupmenu.h"
62
63
#include "gui/windows/bankwindow.h"
64
#include "gui/windows/clanwindow.h"
65
#include "gui/windows/cutinwindow.h"
66
#include "gui/windows/mailwindow.h"
67
#include "gui/windows/chatwindow.h"
68
#include "gui/windows/debugwindow.h"
69
#include "gui/windows/didyouknowwindow.h"
70
#include "gui/windows/emotewindow.h"
71
#include "gui/windows/equipmentwindow.h"
72
#include "gui/windows/inventorywindow.h"
73
#include "gui/windows/killstats.h"
74
#include "gui/windows/minimap.h"
75
#include "gui/windows/ministatuswindow.h"
76
#include "gui/windows/npcdialog.h"
77
#include "gui/windows/outfitwindow.h"
78
#include "gui/windows/setupwindow.h"
79
#include "gui/windows/shopwindow.h"
80
#include "gui/windows/shortcutwindow.h"
81
#include "gui/windows/skilldialog.h"
82
#include "gui/windows/socialwindow.h"
83
#include "gui/windows/statuswindow.h"
84
#include "gui/windows/tradewindow.h"
85
#include "gui/windows/questswindow.h"
86
#include "gui/windows/whoisonline.h"
87
88
#include "gui/widgets/tabs/chat/battletab.h"
89
90
#include "gui/widgets/createwidget.h"
91
#include "gui/widgets/emoteshortcutcontainer.h"
92
#include "gui/widgets/itemshortcutcontainer.h"
93
#include "gui/widgets/spellshortcutcontainer.h"
94
#include "gui/widgets/virtshortcutcontainer.h"
95
96
#include "gui/widgets/tabs/chat/gmtab.h"
97
#include "gui/widgets/tabs/chat/langtab.h"
98
#include "gui/widgets/tabs/chat/tradetab.h"
99
100
#include "input/inputmanager.h"
101
#include "input/joystick.h"
102
#include "input/keyboardconfig.h"
103
104
#include "input/touch/touchmanager.h"
105
106
#include "net/generalhandler.h"
107
#include "net/gamehandler.h"
108
#include "net/net.h"
109
#include "net/packetcounters.h"
110
111
#include "particle/particleengine.h"
112
113
#include "resources/delayedmanager.h"
114
#include "resources/mapreader.h"
115
#include "resources/screenshothelper.h"
116
117
#include "resources/db/mapdb.h"
118
119
#include "resources/map/map.h"
120
121
#include "resources/resourcemanager/resourcemanager.h"
122
123
#include "resources/sprite/animatedsprite.h"
124
125
#include "utils/delete2.h"
126
#include "utils/foreach.h"
127
#include "utils/gettext.h"
128
#include "utils/pnglib.h"
129
#include "utils/sdlcheckutils.h"
130
#include "utils/timer.h"
131
132
#ifdef __native_client__
133
#include "utils/naclmessages.h"
134
#endif  // __native_client__
135
136
#include "listeners/assertlistener.h"
137
#include "listeners/errorlistener.h"
138
139
#ifdef TMWA_SUPPORT
140
#include "net/tmwa/guildmanager.h"
141
#endif  // TMWA_SUPPORT
142
143
#ifdef USE_MUMBLE
144
#include "mumblemanager.h"
145
#endif  // USE_MUMBLE
146
147
#ifdef WIN32
148
#include <sys/time.h>
149
#undef ERROR
150
#endif  // WIN32
151
152
#include <fstream>
153
154
#include "debug.h"
155
156
QuitDialog *quitDialog = nullptr;
157
Window *disconnectedDialog = nullptr;
158
159
bool mStatsReUpdated = false;
160
const time_t adjustDelay = 10;
161
162
/**
163
 * Initialize every game sub-engines in the right order
164
 */
165
static void initEngines()
166
{
167
    actorManager = new ActorManager;
168
    effectManager = new EffectManager;
169
#ifdef TMWA_SUPPORT
170
    GuildManager::init();
171
#endif  // TMWA_SUPPORT
172
173
    crazyMoves = new CrazyMoves;
174
175
    particleEngine = new ParticleEngine;
176
    particleEngine->setMap(nullptr);
177
    particleEngine->setupEngine();
178
    BeingInfo::init();
179
180
    if (gameHandler != nullptr)
181
        gameHandler->initEngines();
182
183
    keyboard.update();
184
    if (joystick != nullptr)
185
        joystick->update();
186
187
    UpdateStatusListener::distributeEvent();
188
}
189
190
/**
191
 * Create all the various globally accessible gui windows
192
 */
193
static void createGuiWindows()
194
{
195
    if (setupWindow != nullptr)
196
        setupWindow->clearWindowsForReset();
197
198
    if (emoteShortcut != nullptr)
199
        emoteShortcut->load();
200
201
    GameModifiers::init();
202
203
    // Create dialogs
204
    CREATEWIDGETV0(emoteWindow, EmoteWindow);
205
    delete2(debugChatTab)
206
    if (chatWindow != nullptr)
207
    {
208
        chatWindow->scheduleDelete();
209
        chatWindow = nullptr;
210
    }
211
    CREATEWIDGETV(chatWindow, ChatWindow,
212
        "Chat");
213
    CREATEWIDGETV0(tradeWindow, TradeWindow);
214
    CREATEWIDGETV(equipmentWindow, EquipmentWindow,
215
        PlayerInfo::getEquipment(),
216
        localPlayer,
217
        false);
218
    CREATEWIDGETV(beingEquipmentWindow, EquipmentWindow,
219
        nullptr,
220
        nullptr,
221
        true);
222
    beingEquipmentWindow->setVisible(Visible_false);
223
    CREATEWIDGETV0(statusWindow, StatusWindow);
224
    CREATEWIDGETV0(miniStatusWindow, MiniStatusWindow);
225
    CREATEWIDGETV(inventoryWindow, InventoryWindow,
226
        PlayerInfo::getInventory());
227
    if (Net::getNetworkType() != ServerType::TMWATHENA)
228
    {
229
        CREATEWIDGETV(cartWindow, InventoryWindow,
230
            PlayerInfo::getCartInventory());
231
    }
232
    CREATEWIDGETV0(cutInWindow, CutInWindow);
233
    CREATEWIDGETV0(shopWindow, ShopWindow);
234
    CREATEWIDGETV0(skillDialog, SkillDialog);
235
    CREATEWIDGETV0(minimap, Minimap);
236
    if (debugWindow != nullptr)
237
    {
238
        debugWindow->scheduleDelete();
239
        debugWindow = nullptr;
240
    }
241
    CREATEWIDGETV(debugWindow, DebugWindow,
242
        "Debug");
243
    CREATEWIDGETV(itemShortcutWindow, ShortcutWindow,
244
        "ItemShortcut", "items.xml", 83, 460);
245
246
    for (unsigned f = 0; f < SHORTCUT_TABS - 1; f ++)
247
    {
248
        itemShortcutWindow->addTab(toString(f + 1),
249
            new ItemShortcutContainer(nullptr, f));
250
    }
251
    if (Net::getNetworkType() != ServerType::TMWATHENA)
252
    {
253
        itemShortcutWindow->addTab("A",
254
            new ItemShortcutContainer(nullptr, SHORTCUT_TABS - 1));
255
    }
256
    if (config.getBoolValue("showDidYouKnow"))
257
    {
258
        didYouKnowWindow->setVisible(Visible_true);
259
        didYouKnowWindow->loadData(0);
260
    }
261
262
    CREATEWIDGETV(emoteShortcutWindow, ShortcutWindow,
263
        "EmoteShortcut",
264
        new EmoteShortcutContainer(nullptr),
265
        "emotes.xml",
266
        130, 480);
267
    CREATEWIDGETV0(outfitWindow, OutfitWindow);
268
    CREATEWIDGETV(dropShortcutWindow, ShortcutWindow,
269
        "DropShortcut",
270
        new VirtShortcutContainer(nullptr, dropShortcut),
271
        "drops.xml",
272
        0, 0);
273
    CREATEWIDGETV(spellShortcutWindow, ShortcutWindow,
274
        "SpellShortcut",
275
        "spells.xml",
276
        265, 328);
277
    for (unsigned f = 0; f < SPELL_SHORTCUT_TABS; f ++)
278
    {
279
        spellShortcutWindow->addTab(toString(f + 1),
280
            new SpellShortcutContainer(nullptr, f));
281
    }
282
283
    CREATEWIDGETV0(bankWindow, BankWindow);
284
    CREATEWIDGETV0(mailWindow, MailWindow);
285
    CREATEWIDGETV0(whoIsOnline, WhoIsOnline);
286
    CREATEWIDGETV0(killStats, KillStats);
287
    CREATEWIDGETV0(socialWindow, SocialWindow);
288
    CREATEWIDGETV0(questsWindow, QuestsWindow);
289
    CREATEWIDGETV0(clanWindow, ClanWindow);
290
291
    // TRANSLATORS: chat tab header
292
    localChatTab = new ChatTab(chatWindow, _("General"),
293
        GENERAL_CHANNEL, "#General", ChatTabType::INPUT);
294
    localChatTab->setAllowHighlight(false);
295
    if (config.getBoolValue("showChatHistory"))
296
        localChatTab->loadFromLogFile("#General");
297
298
    // TRANSLATORS: chat tab header
299
    debugChatTab = new ChatTab(chatWindow, _("Debug"), "",
300
        "#Debug", ChatTabType::DEBUG);
301
    debugChatTab->setAllowHighlight(false);
302
303
    if (assertListener != nullptr)
304
    {
305
        const StringVect &messages = assertListener->getMessages();
306
        FOR_EACH (StringVectCIter, it, messages)
307
        {
308
            debugChatTab->chatLog(*it,
309
                ChatMsgType::BY_SERVER,
310
                IgnoreRecord_false,
311
                TryRemoveColors_true);
312
        }
313
        delete2(assertListener);
314
    }
315
    if (config.getBoolValue("enableTradeTab"))
316
        chatWindow->addSpecialChannelTab(TRADE_CHANNEL, false);
317
    else
318
        tradeChatTab = nullptr;
319
320
    if (config.getBoolValue("enableBattleTab"))
321
    {
322
        battleChatTab = new BattleTab(chatWindow);
323
        battleChatTab->setAllowHighlight(false);
324
    }
325
    else
326
    {
327
        battleChatTab = nullptr;
328
    }
329
330
    chatWindow->showGMTab();
331
    if (!isSafeMode)
332
        chatWindow->loadState();
333
334
    if (setupWindow != nullptr)
335
        setupWindow->externalUpdate();
336
337
    if (localPlayer != nullptr)
338
        localPlayer->updateStatus();
339
340
    if (generalHandler != nullptr)
341
        generalHandler->gameStarted();
342
}
343
344
/**
345
 * Destroy all the globally accessible gui windows
346
 */
347
static void destroyGuiWindows()
348
{
349
    if (generalHandler != nullptr)
350
        generalHandler->gameEnded();
351
352
    if (whoIsOnline != nullptr)
353
        whoIsOnline->setAllowUpdate(false);
354
355
#ifdef TMWA_SUPPORT
356
    GuildManager::clear();
357
#endif  // TMWA_SUPPORT
358
359
    delete2(windowMenu);
360
    delete2(localChatTab)  // Need to do this first, so it can remove itself
361
    delete2(debugChatTab)
362
    delete2(tradeChatTab)
363
    delete2(battleChatTab)
364
    delete2(langChatTab)
365
    delete2(gmChatTab);
366
#ifdef TMWA_SUPPORT
367
    if (guildManager != nullptr && GuildManager::getEnableGuildBot())
368
        guildManager->reload();
369
#endif  // TMWA_SUPPORT
370
371
    logger->log("start deleting");
372
    delete2(emoteWindow);
373
    delete2(chatWindow)
374
    logger->log("end deleting");
375
    delete2(statusWindow)
376
    delete2(miniStatusWindow)
377
    delete2(inventoryWindow)
378
    delete2(cartWindow)
379
    delete2(shopWindow)
380
    delete2(skillDialog)
381
    delete2(minimap)
382
    delete2(equipmentWindow)
383
    delete2(beingEquipmentWindow)
384
    delete2(tradeWindow)
385
    delete2(debugWindow)
386
    delete2(itemShortcutWindow)
387
    delete2(emoteShortcutWindow)
388
    delete2(outfitWindow)
389
    delete2(socialWindow)
390
    delete2(dropShortcutWindow);
391
    delete2(spellShortcutWindow);
392
    delete2(bankWindow);
393
    delete2(cutInWindow);
394
    delete2(mailWindow);
395
    delete2(questsWindow);
396
    delete2(whoIsOnline);
397
    delete2(killStats);
398
    delete2(clanWindow);
399
}
400
401
Game *Game::mInstance = nullptr;
402
403
Game::Game() :
404
    mCurrentMap(nullptr),
405
    mMapName(""),
406
    mValidSpeed(true),
407
    mNextAdjustTime(cur_time + adjustDelay),
408
    mAdjustLevel(0),
409
    mAdjustPerfomance(config.getBoolValue("adjustPerfomance")),
410
    mLowerCounter(0),
411
    mPing(0),
412
    mTime(cur_time + 1),
413
    mTime2(cur_time + 10)
414
{
415
    touchManager.setInGame(true);
416
417
//    assert(!mInstance);
418
    if (mInstance != nullptr)
419
        logger->log("error: double game creation");
420
    mInstance = this;
421
422
    config.incValue("gamecount");
423
424
    disconnectedDialog = nullptr;
425
426
    // Create the viewport
427
    viewport = new Viewport;
428
    viewport->setSize(mainGraphics->mWidth, mainGraphics->mHeight);
429
    PlayerInfo::clear();
430
431
    emptyBeingSlot = new BeingSlot;
432
433
    BasicContainer2 *const top = static_cast<BasicContainer2*>(gui->getTop());
434
    if (top != nullptr)
435
        top->add(viewport);
436
    viewport->requestMoveToBottom();
437
438
    AnimatedSprite::setEnableCache(
439
        mainGraphics->getOpenGL() != RENDER_SOFTWARE &&
440
        config.getBoolValue("enableDelayedAnimations"));
441
442
    CompoundSprite::setEnableDelay(
443
        config.getBoolValue("enableCompoundSpriteDelay"));
444
445
    createGuiWindows();
446
    windowMenu = new WindowMenu(nullptr);
447
448
    if (windowContainer != nullptr)
449
        windowContainer->add(windowMenu);
450
451
#ifdef USE_OPENGL
452
    MapReader::loadEmptyAtlas();
453
#endif  // USE_OPENGL
454
455
    initEngines();
456
457
    chatWindow->postConnection();
458
    mailWindow->postConnection();
459
460
    // Initialize beings
461
    if (actorManager != nullptr)
462
        actorManager->setPlayer(localPlayer);
463
464
    gameHandler->ping(tick_time);
465
466
    if (setupWindow != nullptr)
467
        setupWindow->setInGame(true);
468
    clearKeysArray();
469
470
#ifdef TMWA_SUPPORT
471
    if (guildManager != nullptr && GuildManager::getEnableGuildBot())
472
        guildManager->requestGuildInfo();
473
#endif  // TMWA_SUPPORT
474
475
    settings.disableLoggingInGame = config.getBoolValue(
476
        "disableLoggingInGame");
477
}
478
479
Game::~Game()
480
{
481
#ifdef USE_OPENGL
482
    MapReader::unloadEmptyAtlas();
483
#endif  // USE_OPENGL
484
485
    settings.disableLoggingInGame = false;
486
    touchManager.setInGame(false);
487
    config.write();
488
    serverConfig.write();
489
    resetAdjustLevel();
490
    destroyGuiWindows();
491
492
    AnimatedSprite::setEnableCache(false);
493
494
    delete2(actorManager)
495
    if (client->getState() != State::CHANGE_MAP)
496
        delete2(localPlayer)
497
    if (effectManager != nullptr)
498
        effectManager->clear();
499
    delete2(effectManager)
500
    delete2(particleEngine)
501
    delete2(viewport)
502
    delete2(mCurrentMap)
503
#ifdef TMWA_SUPPORT
504
    delete2(guildManager)
505
#endif  // TMWA_SUPPORT
506
#ifdef USE_MUMBLE
507
    delete2(mumbleManager)
508
#endif  // USE_MUMBLE
509
510
    delete2(crazyMoves);
511
    delete2(emptyBeingSlot);
512
513
    Being::clearCache();
514
    mInstance = nullptr;
515
    PlayerInfo::gameDestroyed();
516
}
517
518
void Game::addWatermark()
519
{
520
    if ((boldFont == nullptr) || !config.getBoolValue("addwatermark"))
521
        return;
522
523
    const Color &color1 = theme->getColor(ThemeColorId::TEXT, 255);
524
    const Color &color2 = theme->getColor(ThemeColorId::TEXT_OUTLINE, 255);
525
526
    boldFont->drawString(mainGraphics,
527
        color1,
528
        color2,
529
        settings.serverName,
530
        100, 50);
531
}
532
533
bool Game::createScreenshot(const std::string &prefix)
534
{
535
    if ((mainGraphics == nullptr) || (screenshortHelper == nullptr))
536
        return false;
537
538
    SDL_Surface *screenshot = nullptr;
539
540
    if (!config.getBoolValue("showip") && (gui != nullptr))
541
    {
542
        mainGraphics->setSecure(true);
543
        screenshortHelper->prepare();
544
        gui->draw();
545
        addWatermark();
546
        screenshot = screenshortHelper->getScreenshot();
547
        mainGraphics->setSecure(false);
548
    }
549
    else
550
    {
551
        addWatermark();
552
        screenshot = screenshortHelper->getScreenshot();
553
    }
554
555
    if (screenshot == nullptr)
556
        return false;
557
558
    return saveScreenshot(screenshot, prefix);
559
}
560
561
bool Game::saveScreenshot(SDL_Surface *const screenshot,
562
                          const std::string &prefix)
563
{
564
    std::string screenshotDirectory = settings.screenshotDir;
565
    if (mkdir_r(screenshotDirectory.c_str()) != 0)
566
    {
567
        logger->log("Directory %s doesn't exist and can't be created! "
568
                    "Setting screenshot directory to home.",
569
                    screenshotDirectory.c_str());
570
        screenshotDirectory = std::string(VirtFs::getUserDir());
571
    }
572
573
    // Search for an unused screenshot name
574
    std::stringstream filename;
575
    std::fstream testExists;
576
577
    time_t rawtime;
578
    char buffer [100];
579
    time(&rawtime);
580
    struct tm *const timeinfo = localtime(&rawtime);
581
    strftime(buffer, 99, "%Y-%m-%d_%H-%M-%S", timeinfo);
582
583
    const std::string serverName = settings.serverName;
584
    std::string screenShortStr;
585
    if (prefix.empty())
586
    {
587
        if (serverName.empty())
588
        {
589
            screenShortStr = strprintf("%s_Screenshot_%s_",
590
                branding.getValue("appName", "ManaPlus").c_str(),
591
                buffer);
592
        }
593
        else
594
        {
595
            screenShortStr = strprintf("%s_Screenshot_%s_%s_",
596
                branding.getValue("appName", "ManaPlus").c_str(),
597
                serverName.c_str(), buffer);
598
        }
599
600
        bool found = false;
601
        static unsigned int screenshotCount = 0;
602
        do
603
        {
604
            screenshotCount++;
605
            filename.str("");
606
            filename << screenshotDirectory << "/";
607
            filename << screenShortStr << screenshotCount << ".png";
608
            testExists.open(filename.str().c_str(), std::ios::in);
609
            found = !testExists.is_open();
610
            testExists.close();
611
        }
612
        while (!found);
613
    }
614
    else
615
    {
616
        screenShortStr = prefix;
617
        filename.str("");
618
        filename << screenshotDirectory << "/";
619
        filename << screenShortStr;
620
    }
621
622
    const std::string fileNameStr = filename.str();
623
    const bool success = PngLib::writePNG(screenshot, fileNameStr);
624
#ifdef __native_client__
625
    std::string nacScreenshotlDir = fileNameStr;
626
    cutFirst(nacScreenshotlDir, "/persistent");
627
    naclPostMessage("copy-from-persistent", nacScreenshotlDir);
628
    logger->log("nacl screenshot path: " + nacScreenshotlDir);
629
#endif  // __native_client__
630
631
    if (success)
632
    {
633
        if (localChatTab != nullptr)
634
        {
635
            // TRANSLATORS: save file message
636
            std::string str = strprintf(_("Screenshot saved as %s"),
637
                fileNameStr.c_str());
638
            localChatTab->chatLog(str,
639
                ChatMsgType::BY_SERVER,
640
                IgnoreRecord_false,
641
                TryRemoveColors_true);
642
        }
643
    }
644
    else
645
    {
646
        if (localChatTab != nullptr)
647
        {
648
            // TRANSLATORS: save file message
649
            localChatTab->chatLog(_("Saving screenshot failed!"),
650
                ChatMsgType::BY_SERVER,
651
                IgnoreRecord_false,
652
                TryRemoveColors_true);
653
        }
654
        logger->log1("Error: could not save screenshot.");
655
    }
656
657
    MSDL_FreeSurface(screenshot);
658
    return success;
659
}
660
661
void Game::logic()
662
{
663
    BLOCK_START("Game::logic")
664
    handleInput();
665
666
    // Handle all necessary game logic
667
    if (actorManager != nullptr)
668
        actorManager->logic();
669
    if (particleEngine != nullptr)
670
        particleEngine->update();
671
    if (mCurrentMap != nullptr)
672
        mCurrentMap->update(1);
673
674
    BLOCK_END("Game::logic")
675
}
676
677
void Game::slowLogic()
678
{
679
    BLOCK_START("Game::slowLogic")
680
    if (localPlayer != nullptr)
681
        localPlayer->slowLogic();
682
    const time_t time = cur_time;
683
    if (mTime != time)
684
    {
685
        if (valTest(Updated))
686
            mValidSpeed = false;
687
688
        mTime = time + 1;
689
        if (debugWindow != nullptr)
690
            debugWindow->slowLogic();
691
        if (killStats != nullptr)
692
            killStats->update();
693
        if (socialWindow != nullptr)
694
            socialWindow->slowLogic();
695
        if (whoIsOnline != nullptr)
696
            whoIsOnline->slowLogic();
697
        Being::reReadConfig();
698
        if (killStats != nullptr)
699
            killStats->recalcStats();
700
701
        if (time > mTime2 || mTime2 - time > 10)
702
        {
703
            mTime2 = time + 10;
704
            config.writeUpdated();
705
            serverConfig.writeUpdated();
706
        }
707
        if (effectManager != nullptr)
708
            effectManager->logic();
709
    }
710
711
    if (mainGraphics->getOpenGL() != RENDER_SOFTWARE)
712
        DelayedManager::delayedLoad();
713
714
#ifdef TMWA_SUPPORT
715
    if (shopWindow != nullptr)
716
        shopWindow->updateTimes();
717
    if (guildManager != nullptr)
718
        guildManager->slowLogic();
719
#endif  // TMWA_SUPPORT
720
721
    if (skillDialog != nullptr)
722
        skillDialog->slowLogic();
723
724
    PacketCounters::update();
725
726
    // Handle network stuff
727
    if (!gameHandler->isConnected())
728
    {
729
        if (client->getState() == State::CHANGE_MAP)
730
            return;  // Not a problem here
731
732
        if (client->getState() != State::ERROR)
733
        {
734
            if (disconnectedDialog == nullptr)
735
            {
736
                // TRANSLATORS: error message text
737
                errorMessage = _("The connection to the server was lost.");
738
                disconnectedDialog = DialogsManager::openErrorDialog(
739
                    // TRANSLATORS: error message header
740
                    _("Network Error"),
741
                    errorMessage,
742
                    Modal_false);
743
                disconnectedDialog->addActionListener(&errorListener);
744
                disconnectedDialog->requestMoveToTop();
745
            }
746
        }
747
748
        if ((viewport != nullptr) && !errorMessage.empty())
749
        {
750
            const Map *const map = viewport->getMap();
751
            if (map != nullptr)
752
                map->saveExtraLayer();
753
        }
754
        DialogsManager::closeDialogs();
755
        WindowManager::setFramerate(config.getIntValue("fpslimit"));
756
        mNextAdjustTime = cur_time + adjustDelay;
757
        if (client->getState() != State::ERROR)
758
            errorMessage.clear();
759
    }
760
    else
761
    {
762
        if (gameHandler->mustPing()
763
            && get_elapsed_time1(mPing) > 3000)
764
        {
765
            mPing = tick_time;
766
            gameHandler->ping(tick_time);
767
        }
768
769
        if (mAdjustPerfomance)
770
            adjustPerfomance();
771
        if (disconnectedDialog != nullptr)
772
        {
773
            disconnectedDialog->scheduleDelete();
774
            disconnectedDialog = nullptr;
775
        }
776
    }
777
    BLOCK_END("Game::slowLogic")
778
}
779
780
void Game::adjustPerfomance()
781
{
782
    FUNC_BLOCK("Game::adjustPerfomance", 1)
783
    const time_t time = cur_time;
784
    if (mNextAdjustTime <= adjustDelay)
785
    {
786
        mNextAdjustTime = time + adjustDelay;
787
    }
788
    else if (mNextAdjustTime < time)
789
    {
790
        mNextAdjustTime = time + adjustDelay;
791
792
        if (mAdjustLevel > 3 ||
793
            localPlayer == nullptr ||
794
            localPlayer->getHalfAway() ||
795
            settings.awayMode)
796
        {
797
            return;
798
        }
799
800
        int maxFps = WindowManager::getFramerate();
801
        if (maxFps != config.getIntValue("fpslimit"))
802
            return;
803
804
        if (maxFps == 0)
805
            maxFps = 30;
806
        else if (maxFps < 10)
807
            return;
808
809
        if (fps < maxFps - 10)
810
        {
811
            if (mLowerCounter < 2)
812
            {
813
                mLowerCounter ++;
814
                mNextAdjustTime = cur_time + 1;
815
                return;
816
            }
817
            mLowerCounter = 0;
818
            mAdjustLevel ++;
819
            switch (mAdjustLevel)
820
            {
821
                case 1:
822
                {
823
                    if (config.getBoolValue("beingopacity"))
824
                    {
825
                        config.setValue("beingopacity", false);
826
                        config.setSilent("beingopacity", true);
827
                        if (localChatTab != nullptr)
828
                        {
829
                            localChatTab->chatLog(
830
                                // TRANSLATORS: auto adjust settings message
831
                                _("Auto disable Show beings transparency"),
832
                                ChatMsgType::BY_SERVER,
833
                                IgnoreRecord_false,
834
                                TryRemoveColors_true);
835
                        }
836
                    }
837
                    else
838
                    {
839
                        mNextAdjustTime = time + 1;
840
                        mLowerCounter = 2;
841
                    }
842
                    break;
843
                }
844
                case 2:
845
                    if (ParticleEngine::emitterSkip < 4)
846
                    {
847
                        ParticleEngine::emitterSkip = 4;
848
                        if (localChatTab != nullptr)
849
                        {
850
                            localChatTab->chatLog(
851
                                // TRANSLATORS: auto adjust settings message
852
                                _("Auto lower Particle effects"),
853
                                ChatMsgType::BY_SERVER,
854
                                IgnoreRecord_false,
855
                                TryRemoveColors_true);
856
                        }
857
                    }
858
                    else
859
                    {
860
                        mNextAdjustTime = time + 1;
861
                        mLowerCounter = 2;
862
                    }
863
                    break;
864
                case 3:
865
                    if (!config.getBoolValue("alphaCache"))
866
                    {
867
                        config.setValue("alphaCache", true);
868
                        config.setSilent("alphaCache", false);
869
                        if (localChatTab != nullptr)
870
                        {
871
                            localChatTab->chatLog(
872
                                // TRANSLATORS: auto adjust settings message
873
                                _("Auto enable opacity cache"),
874
                                ChatMsgType::BY_SERVER,
875
                                IgnoreRecord_false,
876
                                TryRemoveColors_true);
877
                        }
878
                    }
879
                    break;
880
                default:
881
                    break;
882
            }
883
        }
884
    }
885
}
886
887
void Game::resetAdjustLevel()
888
{
889
    if (!mAdjustPerfomance)
890
        return;
891
892
    mNextAdjustTime = cur_time + adjustDelay;
893
    switch (mAdjustLevel)
894
    {
895
        case 1:
896
            config.setValue("beingopacity",
897
                config.getBoolValue("beingopacity"));
898
            break;
899
        case 2:
900
            config.setValue("beingopacity",
901
                config.getBoolValue("beingopacity"));
902
            ParticleEngine::emitterSkip = config.getIntValue(
903
                "particleEmitterSkip") + 1;
904
            break;
905
        default:
906
        case 3:
907
            config.setValue("beingopacity",
908
                config.getBoolValue("beingopacity"));
909
            ParticleEngine::emitterSkip = config.getIntValue(
910
                "particleEmitterSkip") + 1;
911
            config.setValue("alphaCache",
912
                config.getBoolValue("alphaCache"));
913
            break;
914
    }
915
    mAdjustLevel = 0;
916
}
917
918
void Game::handleMove()
919
{
920
    BLOCK_START("Game::handleMove")
921
    if (localPlayer == nullptr)
922
    {
923
        BLOCK_END("Game::handleMove")
924
        return;
925
    }
926
927
    // Moving player around
928
    if ((chatWindow != nullptr) &&
929
        (quitDialog == nullptr) &&
930
        localPlayer->canMove() &&
931
        !chatWindow->isInputFocused() &&
932
        !InventoryWindow::isAnyInputFocused() &&
933
        !popupMenu->isPopupVisible())
934
    {
935
        NpcDialog *const dialog = NpcDialog::getActive();
936
        if (dialog != nullptr)
937
        {
938
            BLOCK_END("Game::handleMove")
939
            return;
940
        }
941
942
        // Ignore input if either "ignore" key is pressed
943
        // Stops the character moving about if the user's window manager
944
        // uses "ignore+arrow key" to switch virtual desktops.
945
        if (inputManager.isActionActive(InputAction::IGNORE_INPUT_1) ||
946
            inputManager.isActionActive(InputAction::IGNORE_INPUT_2))
947
        {
948
            BLOCK_END("Game::handleMove")
949
            return;
950
        }
951
952
        unsigned char direction = 0;
953
954
        // Translate pressed keys to movement and direction
955
        if (inputManager.isActionActive(InputAction::MOVE_UP) ||
956
            ((joystick != nullptr) && joystick->isUp()))
957
        {
958
            direction |= BeingDirection::UP;
959
            setValidSpeed();
960
            localPlayer->cancelFollow();
961
        }
962
        else if (inputManager.isActionActive(InputAction::MOVE_DOWN) ||
963
                 ((joystick != nullptr) && joystick->isDown()))
964
        {
965
            direction |= BeingDirection::DOWN;
966
            setValidSpeed();
967
            localPlayer->cancelFollow();
968
        }
969
970
        if (inputManager.isActionActive(InputAction::MOVE_LEFT) ||
971
            ((joystick != nullptr) && joystick->isLeft()))
972
        {
973
            direction |= BeingDirection::LEFT;
974
            setValidSpeed();
975
            localPlayer->cancelFollow();
976
        }
977
        else if (inputManager.isActionActive(InputAction::MOVE_RIGHT) ||
978
                 ((joystick != nullptr) && joystick->isRight()))
979
        {
980
            direction |= BeingDirection::RIGHT;
981
            setValidSpeed();
982
            localPlayer->cancelFollow();
983
        }
984
        else if (inputManager.isActionActive(InputAction::MOVE_FORWARD))
985
        {
986
            direction = localPlayer->getDirection();
987
            setValidSpeed();
988
            localPlayer->cancelFollow();
989
        }
990
991
        if ((!inputManager.isActionActive(InputAction::EMOTE)
992
            && !inputManager.isActionActive(InputAction::PET_EMOTE)
993
            && !inputManager.isActionActive(InputAction::HOMUN_EMOTE)
994
            && !inputManager.isActionActive(InputAction::STOP_ATTACK))
995
            || direction == 0)
996
        {
997
            moveInDirection(direction);
998
        }
999
    }
1000
    BLOCK_END("Game::handleMove")
1001
}
1002
1003
void Game::moveInDirection(const unsigned char direction)
1004
{
1005
    if (viewport == nullptr)
1006
        return;
1007
1008
    if (settings.cameraMode == 0u)
1009
    {
1010
        if (localPlayer != nullptr)
1011
            localPlayer->specialMove(direction);
1012
    }
1013
    else
1014
    {
1015
        int dx = 0;
1016
        int dy = 0;
1017
        if ((direction & BeingDirection::LEFT) != 0)
1018
            dx = -5;
1019
        else if ((direction & BeingDirection::RIGHT) != 0)
1020
            dx = 5;
1021
1022
        if ((direction & BeingDirection::UP) != 0)
1023
            dy = -5;
1024
        else if ((direction & BeingDirection::DOWN) != 0)
1025
            dy = 5;
1026
        viewport->moveCamera(dx, dy);
1027
    }
1028
}
1029
1030
void Game::updateFrameRate(int fpsLimit)
1031
{
1032
    if (fpsLimit == 0)
1033
    {
1034
        if (settings.awayMode)
1035
        {
1036
            if (settings.inputFocused != KeyboardFocus::Unfocused ||
1037
                settings.mouseFocused)
1038
            {
1039
                fpsLimit = config.getIntValue("fpslimit");
1040
            }
1041
            else
1042
            {
1043
                fpsLimit = config.getIntValue("altfpslimit");
1044
            }
1045
        }
1046
        else
1047
        {
1048
            fpsLimit = config.getIntValue("fpslimit");
1049
        }
1050
    }
1051
    WindowManager::setFramerate(fpsLimit);
1052
    mNextAdjustTime = cur_time + adjustDelay;
1053
}
1054
1055
/**
1056
 * The huge input handling method.
1057
 */
1058
void Game::handleInput()
1059
{
1060
    BLOCK_START("Game::handleInput 1")
1061
    if (joystick != nullptr)
1062
        joystick->logic();
1063
1064
    eventsManager.handleGameEvents();
1065
1066
    // If the user is configuring the keys then don't respond.
1067
    if (!keyboard.isEnabled() || settings.awayMode)
1068
    {
1069
        BLOCK_END("Game::handleInput 1")
1070
        return;
1071
    }
1072
1073
    // If pressed outfits keys, stop processing keys.
1074
    if (inputManager.isActionActive(InputAction::WEAR_OUTFIT)
1075
        || inputManager.isActionActive(InputAction::COPY_OUTFIT)
1076
        || ((setupWindow != nullptr) && setupWindow->isWindowVisible()))
1077
    {
1078
        BLOCK_END("Game::handleInput 1")
1079
        return;
1080
    }
1081
1082
    handleMove();
1083
    InputManager::handleRepeat();
1084
    BLOCK_END("Game::handleInput 1")
1085
}
1086
1087
/**
1088
 * Changes the currently active map. Should only be called while the game is
1089
 * running.
1090
 */
1091
void Game::changeMap(const std::string &mapPath)
1092
{
1093
    BLOCK_START("Game::changeMap")
1094
1095
    resetAdjustLevel();
1096
    ResourceManager::cleanProtected();
1097
1098
    PopupManager::clearPopup();
1099
    PopupManager::closePopupMenu();
1100
1101
    // Clean up floor items, beings and particles
1102
    if (actorManager != nullptr)
1103
        actorManager->clear();
1104
1105
    // Close the popup menu on map change so that invalid options can't be
1106
    // executed.
1107
    if (viewport != nullptr)
1108
        viewport->cleanHoverItems();
1109
1110
    // Unset the map of the player so that its particles are cleared before
1111
    // being deleted in the next step
1112
    if (localPlayer != nullptr)
1113
        localPlayer->setMap(nullptr);
1114
1115
    if (particleEngine != nullptr)
1116
        particleEngine->clear();
1117
1118
    mMapName = mapPath;
1119
1120
    std::string fullMap = pathJoin(paths.getValue("maps", "maps/"),
1121
        mMapName).append(".tmx");
1122
    std::string realFullMap = pathJoin(paths.getValue("maps", "maps/"),
1123
        MapDB::getMapName(mMapName)).append(".tmx");
1124
1125
    if (!VirtFs::exists(realFullMap))
1126
        realFullMap.append(".gz");
1127
1128
    // Attempt to load the new map
1129
    Map *const newMap = MapReader::readMap(fullMap, realFullMap);
1130
1131
    if (mCurrentMap != nullptr)
1132
        mCurrentMap->saveExtraLayer();
1133
1134
    if (newMap != nullptr)
1135
        newMap->addExtraLayer();
1136
1137
    if (socialWindow != nullptr)
1138
        socialWindow->setMap(newMap);
1139
1140
    // Notify the minimap and actorManager about the map change
1141
    if (minimap != nullptr)
1142
        minimap->setMap(newMap);
1143
    if (actorManager != nullptr)
1144
        actorManager->setMap(newMap);
1145
    if (particleEngine != nullptr)
1146
        particleEngine->setMap(newMap);
1147
    if (viewport != nullptr)
1148
        viewport->setMap(newMap);
1149
1150
    // Initialize map-based particle effects
1151
    if (newMap != nullptr)
1152
        newMap->initializeParticleEffects();
1153
1154
    // Start playing new music file when necessary
1155
    const std::string oldMusic = mCurrentMap != nullptr
1156
        ? mCurrentMap->getMusicFile() : "";
1157
    const std::string newMusic = newMap != nullptr ?
1158
        newMap->getMusicFile() : "";
1159
    if (newMusic != oldMusic)
1160
    {
1161
        if (newMusic.empty())
1162
            soundManager.fadeOutMusic(1000);
1163
        else
1164
            soundManager.fadeOutAndPlayMusic(newMusic, 1000);
1165
    }
1166
1167
    if (mCurrentMap != nullptr)
1168
        mCurrentMap->saveExtraLayer();
1169
1170
    delete mCurrentMap;
1171
    mCurrentMap = newMap;
1172
1173
    if (questsWindow != nullptr)
1174
        questsWindow->setMap(mCurrentMap);
1175
1176
#ifdef USE_MUMBLE
1177
    if (mumbleManager)
1178
        mumbleManager->setMap(mapPath);
1179
#endif  // USE_MUMBLE
1180
1181
    if (localPlayer != nullptr)
1182
        localPlayer->recreateItemParticles();
1183
1184
    gameHandler->mapLoadedEvent();
1185
    BLOCK_END("Game::changeMap")
1186
}
1187
1188
void Game::updateHistory(const SDL_Event &event)
1189
{
1190
    if ((localPlayer == nullptr) || (settings.attackType == 0u))
1191
        return;
1192
1193
    if (CAST_S32(event.key.keysym.sym) != -1)
1194
    {
1195
        bool old = false;
1196
1197
        const InputActionT key = KeyboardConfig::getKeyIndex(event, 1);
1198
        const time_t time = cur_time;
1199
        int idx = -1;
1200
        for (int f = 0; f < MAX_LASTKEYS; f ++)
1201
        {
1202
            LastKey &lastKey = mLastKeys[f];
1203
            if (lastKey.key == key)
1204
            {
1205
                idx = f;
1206
                old = true;
1207
                break;
1208
            }
1209
            else if (idx >= 0 && lastKey.time < mLastKeys[idx].time)
1210
            {
1211
                idx = f;
1212
            }
1213
        }
1214
        if (idx < 0)
1215
        {
1216
            idx = 0;
1217
            for (int f = 0; f < MAX_LASTKEYS; f ++)
1218
            {
1219
                LastKey &lastKey = mLastKeys[f];
1220
                if (lastKey.key == InputAction::NO_VALUE ||
1221
                    lastKey.time < mLastKeys[idx].time)
1222
                {
1223
                    idx = f;
1224
                }
1225
            }
1226
        }
1227
1228
        if (idx < 0)
1229
            idx = 0;
1230
1231
        LastKey &keyIdx = mLastKeys[idx];
1232
        if (!old)
1233
        {
1234
            keyIdx.time = time;
1235
            keyIdx.key = key;
1236
            keyIdx.cnt = 0;
1237
        }
1238
        else
1239
        {
1240
            keyIdx.cnt++;
1241
        }
1242
    }
1243
}
1244
1245
void Game::checkKeys()
1246
{
1247
    const int timeRange = 120;
1248
    const int cntInTime = 130;
1249
1250
    if ((localPlayer == nullptr) || (settings.attackType == 0u))
1251
        return;
1252
1253
    const time_t time = cur_time;
1254
    for (int f = 0; f < MAX_LASTKEYS; f ++)
1255
    {
1256
        LastKey &lastKey = mLastKeys[f];
1257
        if (lastKey.key != InputAction::NO_VALUE)
1258
        {
1259
            if (lastKey.time + timeRange < time)
1260
            {
1261
                if (lastKey.cnt > cntInTime)
1262
                    mValidSpeed = false;
1263
                lastKey.key = InputAction::NO_VALUE;
1264
            }
1265
        }
1266
    }
1267
}
1268
1269
void Game::setValidSpeed()
1270
{
1271
    clearKeysArray();
1272
    mValidSpeed = true;
1273
}
1274
1275
void Game::clearKeysArray()
1276
{
1277
    for (int f = 0; f < MAX_LASTKEYS; f ++)
1278
    {
1279
        mLastKeys[f].time = 0;
1280
        mLastKeys[f].key = InputAction::NO_VALUE;
1281
        mLastKeys[f].cnt = 0;
1282
    }
1283
}
1284
1285
void Game::videoResized(const int width, const int height)
1286
{
1287
    if (viewport != nullptr)
1288
        viewport->setSize(width, height);
1289
    if (windowMenu != nullptr)
1290
        windowMenu->setPosition(width - windowMenu->getWidth(), 0);
1291

6
}