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

3
}