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

6
}