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

3
}