GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/localplayer.cpp Lines: 108 1314 8.2 %
Date: 2021-03-17 Branches: 115 1727 6.7 %

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 "being/localplayer.h"
25
26
#include "actormanager.h"
27
#include "configuration.h"
28
#include "gamemodifiers.h"
29
#include "guild.h"
30
#include "party.h"
31
#include "settings.h"
32
#include "soundmanager.h"
33
#include "statuseffect.h"
34
35
#include "being/beingflag.h"
36
#include "being/crazymoves.h"
37
#include "being/playerinfo.h"
38
#include "being/playerrelations.h"
39
40
#include "const/sound.h"
41
42
#include "enums/equipslot.h"
43
44
#include "enums/being/beingdirection.h"
45
46
#include "enums/resources/map/blockmask.h"
47
#include "enums/resources/map/mapitemtype.h"
48
49
#include "particle/particleengine.h"
50
51
#include "input/keyboardconfig.h"
52
53
#include "gui/gui.h"
54
#include "gui/userpalette.h"
55
#include "gui/popupmanager.h"
56
57
#include "gui/windows/chatwindow.h"
58
#include "gui/windows/ministatuswindow.h"
59
#include "gui/windows/okdialog.h"
60
#include "gui/windows/outfitwindow.h"
61
#include "gui/windows/shopwindow.h"
62
#include "gui/windows/socialwindow.h"
63
#include "gui/windows/statuswindow.h"
64
#include "gui/windows/updaterwindow.h"
65
66
#include "gui/widgets/tabs/chat/whispertab.h"
67
68
#include "listeners/awaylistener.h"
69
70
#include "net/beinghandler.h"
71
#include "net/chathandler.h"
72
#include "net/inventoryhandler.h"
73
#include "net/net.h"
74
#include "net/packetlimiter.h"
75
#include "net/playerhandler.h"
76
#include "net/serverfeatures.h"
77
78
#include "resources/iteminfo.h"
79
80
#include "resources/db/weaponsdb.h"
81
82
#include "resources/item/item.h"
83
84
#include "resources/map/map.h"
85
#include "resources/map/mapitem.h"
86
#include "resources/map/speciallayer.h"
87
#include "resources/map/walklayer.h"
88
89
#include "resources/sprite/animatedsprite.h"
90
91
#include "utils/delete2.h"
92
#include "utils/foreach.h"
93
#include "utils/gettext.h"
94
#include "utils/timer.h"
95
96
#ifdef USE_MUMBLE
97
#include "mumblemanager.h"
98
#endif  // USE_MUMBLE
99
100
#include <climits>
101
102
#include "debug.h"
103
104
static const int16_t awayLimitTimer = 60;
105
static const int MAX_TICK_VALUE = INT_MAX / 2;
106
107
typedef std::map<int, Guild*>::const_iterator GuildMapCIter;
108
109
LocalPlayer *localPlayer = nullptr;
110
111
extern OkDialog *weightNotice;
112
extern time_t weightNoticeTime;
113
114
94
LocalPlayer::LocalPlayer(const BeingId id,
115
94
                         const BeingTypeId subType) :
116
    Being(id, ActorType::Player),
117
    ActorSpriteListener(),
118
    AttributeListener(),
119
    PlayerDeathListener(),
120
    mMoveState(0),
121
    mLastTargetX(0),
122
    mLastTargetY(0),
123
    mHomes(),
124
    mTarget(nullptr),
125
    mPlayerFollowed(),
126
    mPlayerImitated(),
127
    mNextDestX(0),
128
    mNextDestY(0),
129
    mPickUpTarget(nullptr),
130
    mLastAction(-1),
131
    mStatusEffectIcons(),
132
    mMessages(),
133
    mMessageTime(0),
134
✓✗
188
    mAwayListener(new AwayListener),
135
    mAwayDialog(nullptr),
136
    mPingSendTick(0),
137
    mPingTime(0),
138
    mAfkTime(0),
139
    mActivityTime(0),
140
    mNavigateX(0),
141
    mNavigateY(0),
142
    mNavigateId(BeingId_zero),
143
    mCrossX(0),
144
    mCrossY(0),
145
    mOldX(0),
146
    mOldY(0),
147
    mOldTileX(0),
148
    mOldTileY(0),
149
    mNavigatePath(),
150
    mLastHitFrom(),
151
    mWaitFor(),
152
    mAdvertTime(0),
153
    mTestParticle(nullptr),
154
    mTestParticleName(),
155
    mTestParticleTime(0),
156
    mTestParticleHash(0L),
157
✓✗✓✗
376
    mSyncPlayerMoveDistance(config.getIntValue("syncPlayerMoveDistance")),
158
    mUnfreezeTime(0),
159
    mWalkingDir(0),
160
    mUpdateName(true),
161
    mBlockAdvert(false),
162
✓✗✓✗
376
    mTargetDeadPlayers(config.getBoolValue("targetDeadPlayers")),
163
✓✗✓✗
✗✓
376
    mServerAttack(fromBool(config.getBoolValue("serverAttack"), Keep)),
164
94
    mVisibleNames(static_cast<VisibleName::Type>(
165
✓✗✓✗
376
        config.getIntValue("visiblenames"))),
166
✓✗✓✗
376
    mEnableAdvert(config.getBoolValue("enableAdvert")),
167
✓✗✓✗
376
    mTradebot(config.getBoolValue("tradebot")),
168
✓✗✓✗
376
    mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")),
169
✓✗✓✗
376
    mIsServerBuggy(serverConfig.getValueBool("enableBuggyServers", true)),
170
✓✗✓✗
376
    mSyncPlayerMove(config.getBoolValue("syncPlayerMove")),
171
✓✗✓✗
376
    mDrawPath(config.getBoolValue("drawPath")),
172
✓✗✓✗
376
    mAttackMoving(config.getBoolValue("attackMoving")),
173
✓✗✓✗
376
    mAttackNext(config.getBoolValue("attackNext")),
174
✓✗✓✗
376
    mShowJobExp(config.getBoolValue("showJobExp")),
175
✓✗✓✗
376
    mShowServerPos(config.getBoolValue("showserverpos")),
176
    mNextStep(false),
177
    mGoingToTarget(false),
178
    mKeepAttacking(false),
179
    mPathSetByMouse(false),
180
    mWaitPing(false),
181
    mShowNavigePath(false),
182
    mAllowRename(false),
183
✓✗✓✗
2632
    mFreezed(false)
184
{
185
✓✗
94
    logger->log1("LocalPlayer::LocalPlayer");
186
187
#ifdef TMWA_SUPPORT
188
✓✗✗✓
94
    if (Net::getNetworkType() == ServerType::TMWATHENA)
189
    {
190
        mSyncPlayerMoveDistance =
191
            config.getIntValue("syncPlayerMoveDistanceLegacy");
192
    }
193
#endif
194
195
✓✗
94
    postInit(subType, nullptr);
196
94
    mAttackRange = 0;
197
94
    mLevel = 1;
198
94
    mAdvanced = true;
199
✓✗
94
    mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
200
✓✓
94
    if (userPalette != nullptr)
201
✓✗
69
        mNameColor = &userPalette->getColor(UserColorId::SELF, 255U);
202
    else
203
25
        mNameColor = nullptr;
204
205
188
    PlayerInfo::setStatBase(Attributes::PLAYER_WALK_SPEED,
206
        getWalkSpeed(),
207
✓✗
94
        Notify_true);
208
    PlayerInfo::setStatMod(Attributes::PLAYER_WALK_SPEED,
209
        0,
210
✓✗
94
        Notify_true);
211
212
✓✗
94
    loadHomes();
213
214
✓✗✓✗
376
    config.addListener("showownname", this);
215
✓✗✓✗
376
    config.addListener("targetDeadPlayers", this);
216
✓✗✓✗
376
    serverConfig.addListener("enableBuggyServers", this);
217
✓✗✓✗
376
    config.addListener("syncPlayerMove", this);
218
✓✗✓✗
376
    config.addListener("syncPlayerMoveDistance", this);
219
#ifdef TMWA_SUPPORT
220
✓✗✓✗
376
    config.addListener("syncPlayerMoveDistanceLegacy", this);
221
#endif
222
✓✗✓✗
376
    config.addListener("drawPath", this);
223
✓✗✓✗
376
    config.addListener("serverAttack", this);
224
✓✗✓✗
376
    config.addListener("attackMoving", this);
225
✓✗✓✗
376
    config.addListener("attackNext", this);
226
✓✗✓✗
376
    config.addListener("showJobExp", this);
227
✓✗✓✗
376
    config.addListener("enableAdvert", this);
228
✓✗✓✗
376
    config.addListener("tradebot", this);
229
✓✗✓✗
376
    config.addListener("targetOnlyReachable", this);
230
✓✗✓✗
376
    config.addListener("showserverpos", this);
231
✓✗✓✗
376
    config.addListener("visiblenames", this);
232
✓✗✓✗
✓✗
376
    setShowName(config.getBoolValue("showownname"));
233
94
}
234
235
1222
LocalPlayer::~LocalPlayer()
236
{
237
94
    logger->log1("LocalPlayer::~LocalPlayer");
238
239
94
    config.removeListeners(this);
240
376
    serverConfig.removeListener("enableBuggyServers", this);
241
242
94
    navigateClean();
243
94
    mCrossX = 0;
244
94
    mCrossY = 0;
245
246
94
    updateNavigateList();
247
248
✗✓
94
    if (mAwayDialog != nullptr)
249
    {
250
        soundManager.volumeRestore();
251
        delete2(mAwayDialog)
252
    }
253
✓✗
188
    delete2(mAwayListener)
254
188
}
255
256
void LocalPlayer::logic()
257
{
258
    BLOCK_START("LocalPlayer::logic")
259
#ifdef USE_MUMBLE
260
    if (mumbleManager)
261
        mumbleManager->setPos(mX, mY, mDirection);
262
#endif  // USE_MUMBLE
263
264
    // Actions are allowed once per second
265
    if (get_elapsed_time(mLastAction) >= 1000)
266
        mLastAction = -1;
267
268
    if (mActivityTime == 0 || mLastAction != -1)
269
        mActivityTime = cur_time;
270
271
    if (mUnfreezeTime > 0 &&
272
        mUnfreezeTime <= tick_time)
273
    {
274
        mUnfreezeTime = 0;
275
        mFreezed = false;
276
    }
277
278
    if ((mAction != BeingAction::MOVE || mNextStep) && !mNavigatePath.empty())
279
    {
280
        mNextStep = false;
281
        int dist = 5;
282
        if (!mSyncPlayerMove)
283
            dist = 20;
284
285
        if (((mNavigateX != 0) || (mNavigateY != 0)) &&
286
            ((mCrossX + dist >= mX && mCrossX <= mX + dist
287
            && mCrossY + dist >= mY && mCrossY <= mY + dist)
288
            || ((mCrossX == 0) && (mCrossY == 0))))
289
        {
290
            const Path::const_iterator i = mNavigatePath.begin();
291
            if ((*i).x == mX && (*i).y == mY)
292
                mNavigatePath.pop_front();
293
            else
294
                setDestination((*i).x, (*i).y);
295
        }
296
    }
297
298
    // Show XP messages
299
    if (!mMessages.empty())
300
    {
301
        if (mMessageTime == 0)
302
        {
303
            const MessagePair info = mMessages.front();
304
305
            if ((particleEngine != nullptr) && (gui != nullptr))
306
            {
307
                particleEngine->addTextRiseFadeOutEffect(
308
                    info.first,
309
                    mPixelX,
310
                    mPixelY - 48,
311
                    &userPalette->getColor(info.second, 255U),
312
                    gui->getInfoParticleFont(),
313
                    true);
314
            }
315
316
            mMessages.pop_front();
317
            mMessageTime = 30;
318
        }
319
        mMessageTime--;
320
    }
321
322
    if (mTarget != nullptr)
323
    {
324
        if (mTarget->getType() == ActorType::Npc)
325
        {
326
            // NPCs are always in range
327
            mTarget->setTargetType(TargetCursorType::IN_RANGE);
328
        }
329
        else
330
        {
331
            // Find whether target is in range
332
            const int rangeX = CAST_S32(
333
                abs(mTarget->mX - mX));
334
            const int rangeY = CAST_S32(
335
                abs(mTarget->mY - mY));
336
            const int attackRange = getAttackRange();
337
            const TargetCursorTypeT targetType
338
                = rangeX > attackRange || rangeY > attackRange
339
                ? TargetCursorType::NORMAL : TargetCursorType::IN_RANGE;
340
            mTarget->setTargetType(targetType);
341
342
            if (!mTarget->isAlive() && (!mTargetDeadPlayers
343
                || mTarget->getType() != ActorType::Player))
344
            {
345
                stopAttack(true);
346
            }
347
348
            if (mKeepAttacking && (mTarget != nullptr))
349
                attack(mTarget, true, false);
350
        }
351
    }
352
353
    Being::logic();
354
    BLOCK_END("LocalPlayer::logic")
355
}
356
357
void LocalPlayer::slowLogic()
358
{
359
    BLOCK_START("LocalPlayer::slowLogic")
360
    const time_t time = cur_time;
361
    if ((weightNotice != nullptr) && weightNoticeTime < time)
362
    {
363
        weightNotice->scheduleDelete();
364
        weightNotice = nullptr;
365
        weightNoticeTime = 0;
366
    }
367
368
    if ((serverFeatures != nullptr) &&
369
        !serverFeatures->havePlayerStatusUpdate() &&
370
        mEnableAdvert &&
371
        !mBlockAdvert &&
372
        mAdvertTime < cur_time)
373
    {
374
        uint8_t smile = BeingFlag::SPECIAL;
375
        if (mTradebot &&
376
            shopWindow != nullptr &&
377
            !shopWindow->isShopEmpty())
378
        {
379
            smile |= BeingFlag::SHOP;
380
        }
381
382
        if (settings.awayMode || settings.pseudoAwayMode)
383
            smile |= BeingFlag::AWAY;
384
385
        if (mInactive)
386
            smile |= BeingFlag::INACTIVE;
387
388
        if (emote(smile))
389
            mAdvertTime = time + 60;
390
        else
391
            mAdvertTime = time + 30;
392
    }
393
394
    if (mTestParticleTime != time && !mTestParticleName.empty())
395
    {
396
        const unsigned long hash = UpdaterWindow::getFileHash(
397
            mTestParticleName);
398
        if (hash != mTestParticleHash)
399
        {
400
            setTestParticle(mTestParticleName, false);
401
            mTestParticleHash = hash;
402
        }
403
        mTestParticleTime = time;
404
    }
405
406
    BLOCK_END("LocalPlayer::slowLogic")
407
}
408
409
void LocalPlayer::setAction(const BeingActionT &action,
410
                            const int attackId)
411
{
412
    if (action == BeingAction::DEAD)
413
    {
414
        if (!mLastHitFrom.empty() &&
415
            !serverFeatures->haveKillerId())
416
        {
417
            // TRANSLATORS: chat message after death
418
            debugMsg(strprintf(_("You were killed by %s."),
419
                mLastHitFrom.c_str()))
420
            mLastHitFrom.clear();
421
        }
422
        setTarget(nullptr);
423
    }
424
425
    Being::setAction(action,
426
        attackId);
427
#ifdef USE_MUMBLE
428
    if (mumbleManager)
429
        mumbleManager->setAction(CAST_S32(action));
430
#endif  // USE_MUMBLE
431
}
432
433
void LocalPlayer::setGroupId(const int id)
434
{
435
    Being::setGroupId(id);
436
437
    if (mIsGM != 0)
438
    {
439
        if (chatWindow != nullptr)
440
        {
441
            chatWindow->loadGMCommands();
442
            chatWindow->showGMTab();
443
        }
444
    }
445
    if (statusWindow != nullptr)
446
        statusWindow->updateLevelLabel();
447
}
448
449
void LocalPlayer::nextTile()
450
{
451
    const Party *const party = Party::getParty(1);
452
    if (party != nullptr)
453
    {
454
        PartyMember *const pm = party->getMember(mName);
455
        if (pm != nullptr)
456
        {
457
            pm->setX(mX);
458
            pm->setY(mY);
459
        }
460
    }
461
462
    if (mPath.empty())
463
    {
464
        if (mPickUpTarget != nullptr)
465
            pickUp(mPickUpTarget);
466
467
        if (mWalkingDir != 0U)
468
            startWalking(mWalkingDir);
469
    }
470
    else if (mPath.size() == 1)
471
    {
472
        if (mPickUpTarget != nullptr)
473
            pickUp(mPickUpTarget);
474
    }
475
476
    if (mGoingToTarget &&
477
        mTarget != nullptr &&
478
        withinAttackRange(mTarget, false, 0))
479
    {
480
        mAction = BeingAction::STAND;
481
        attack(mTarget, true, false);
482
        mGoingToTarget = false;
483
        mPath.clear();
484
        return;
485
    }
486
    else if (mGoingToTarget && (mTarget == nullptr))
487
    {
488
        mGoingToTarget = false;
489
        mPath.clear();
490
    }
491
492
    if (mPath.empty())
493
    {
494
        if (mNavigatePath.empty() || mAction != BeingAction::MOVE)
495
        {
496
            setAction(BeingAction::STAND, 0);
497
            // +++ probably sync position here always?
498
        }
499
        else
500
        {
501
            mNextStep = true;
502
        }
503
    }
504
    else
505
    {
506
        Being::nextTile();
507
    }
508
509
    fixPos();
510
}
511
512
bool LocalPlayer::pickUp(FloorItem *const item)
513
{
514
    if (item == nullptr)
515
        return false;
516
517
    if (!PacketLimiter::limitPackets(PacketType::PACKET_PICKUP))
518
        return false;
519
520
    const int dx = item->getTileX() - mX;
521
    const int dy = item->getTileY() - mY;
522
    int dist = 6;
523
524
    const unsigned int pickUpType = settings.pickUpType;
525
    if (pickUpType >= 4 && pickUpType <= 6)
526
        dist = 4;
527
528
    if (dx * dx + dy * dy < dist)
529
    {
530
        if ((actorManager != nullptr) && actorManager->checkForPickup(item))
531
        {
532
            PlayerInfo::pickUpItem(item, Sfx_true);
533
            mPickUpTarget = nullptr;
534
        }
535
    }
536
    else if (pickUpType >= 4 && pickUpType <= 6)
537
    {
538
        const Path debugPath = mMap->findPath(
539
            (mPixelX - mapTileSize / 2) / mapTileSize,
540
            (mPixelY - mapTileSize) / mapTileSize,
541
            item->getTileX(),
542
            item->getTileY(),
543
            getBlockWalkMask(),
544
            0);
545
        if (!debugPath.empty())
546
            navigateTo(item->getTileX(), item->getTileY());
547
        else
548
            setDestination(item->getTileX(), item->getTileY());
549
550
        mPickUpTarget = item;
551
        mPickUpTarget->addActorSpriteListener(this);
552
    }
553
    return true;
554
}
555
556
void LocalPlayer::actorSpriteDestroyed(const ActorSprite &actorSprite)
557
{
558
    if (mPickUpTarget == &actorSprite)
559
        mPickUpTarget = nullptr;
560
}
561
562
22
Being *LocalPlayer::getTarget() const
563
{
564
22
    return mTarget;
565
}
566
567
7
void LocalPlayer::setTarget(Being *const target)
568
{
569
✗✓✗✗
7
    if (target == this && (target != nullptr))
570
        return;
571
572
✓✓
7
    if (target == mTarget)
573
        return;
574
575
4
    Being *oldTarget = nullptr;
576
✓✓
4
    if (mTarget != nullptr)
577
    {
578
2
        mTarget->untarget();
579
2
        oldTarget = mTarget;
580
    }
581
582
✓✓
4
    if (mTarget != nullptr)
583
    {
584
✓✓
4
        if (mTarget->getType() == ActorType::Monster)
585
1
            mTarget->setShowName(false);
586
    }
587
588
4
    mTarget = target;
589
590
✓✓
4
    if (oldTarget != nullptr)
591
2
        oldTarget->updateName();
592
593
✓✓
4
    if (target != nullptr)
594
    {
595
2
        mLastTargetX = target->mX;
596
2
        mLastTargetY = target->mY;
597
2
        target->updateName();
598
✗✓
2
        if (mVisibleNames == VisibleName::ShowOnSelection)
599
            target->setShowName(true);
600
    }
601
✓✓✗✓
4
    if (oldTarget != nullptr && mVisibleNames == VisibleName::ShowOnSelection)
602
        oldTarget->setShowName(false);
603
✓✓✓✓
✓✓
6
    if (target != nullptr && target->getType() == ActorType::Monster)
604
1
        target->setShowName(true);
605
}
606
607
Being *LocalPlayer::setNewTarget(const ActorTypeT type,
608
                                 const AllowSort allowSort)
609
{
610
    if (actorManager != nullptr)
611
    {
612
        Being *const target = actorManager->findNearestLivingBeing(
613
            localPlayer, 20, type, allowSort);
614
615
        if ((target != nullptr) && target != mTarget)
616
            setTarget(target);
617
618
        return target;
619
    }
620
    return nullptr;
621
}
622
623
void LocalPlayer::setDestination(const int x, const int y)
624
{
625
    mActivityTime = cur_time;
626
627
    if (settings.attackType == 0 || !mAttackMoving)
628
        mKeepAttacking = false;
629
630
    // Only send a new message to the server when destination changes
631
    if (x != mDest.x || y != mDest.y)
632
    {
633
        if (settings.moveType != 1)
634
        {
635
            playerHandler->setDestination(x, y, mDirection);
636
            Being::setDestination(x, y);
637
        }
638
        else
639
        {
640
            uint8_t newDir = 0;
641
            if ((mDirection & BeingDirection::UP) != 0)
642
                newDir |= BeingDirection::DOWN;
643
            if ((mDirection & BeingDirection::LEFT) != 0)
644
                newDir |= BeingDirection::RIGHT;
645
            if ((mDirection & BeingDirection::DOWN) != 0)
646
                newDir |= BeingDirection::UP;
647
            if ((mDirection & BeingDirection::RIGHT) != 0)
648
                newDir |= BeingDirection::LEFT;
649
650
            playerHandler->setDestination(x, y, newDir);
651
652
//            if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION))
653
            {
654
                setDirection(newDir);
655
                playerHandler->setDirection(newDir);
656
            }
657
658
            Being::setDestination(x, y);
659
            playerHandler->setDestination(x, y, mDirection);
660
        }
661
    }
662
}
663
664
void LocalPlayer::setWalkingDir(const unsigned char dir)
665
{
666
    // This function is called by Game::handleInput()
667
    mWalkingDir = dir;
668
669
    // If we're not already walking, start walking.
670
    if (mAction != BeingAction::MOVE && (dir != 0U))
671
        startWalking(dir);
672
}
673
674
void LocalPlayer::startWalking(const unsigned char dir)
675
{
676
    // This function is called by setWalkingDir(),
677
    // but also by nextTile() for TMW-Athena...
678
    if ((mMap == nullptr) || (dir == 0U))
679
        return;
680
681
    mPickUpTarget = nullptr;
682
    if (mAction == BeingAction::MOVE && !mPath.empty())
683
    {
684
        // Just finish the current action, otherwise we get out of sync
685
        Being::setDestination(mX, mY);
686
        return;
687
    }
688
689
    int dx = 0;
690
    int dy = 0;
691
    if ((dir & BeingDirection::UP) != 0)
692
        dy--;
693
    if ((dir & BeingDirection::DOWN) != 0)
694
        dy++;
695
    if ((dir & BeingDirection::LEFT) != 0)
696
        dx--;
697
    if ((dir & BeingDirection::RIGHT) != 0)
698
        dx++;
699
700
    const unsigned char blockWalkMask = getBlockWalkMask();
701
    // Prevent skipping corners over colliding tiles
702
    if ((dx != 0) && !mMap->getWalk(mX + dx, mY, blockWalkMask))
703
        dx = 0;
704
    if ((dy != 0) && !mMap->getWalk(mX, mY + dy, blockWalkMask))
705
        dy = 0;
706
707
    // Choose a straight direction when diagonal target is blocked
708
    if (dx != 0 && dy != 0 && !mMap->getWalk(mX + dx, mY + dy, blockWalkMask))
709
        dx = 0;
710
711
    // Walk to where the player can actually go
712
    if ((dx != 0 || dy != 0) && mMap->getWalk(mX + dx, mY + dy, blockWalkMask))
713
    {
714
        setDestination(mX + dx, mY + dy);
715
    }
716
    else if (dir != mDirection)
717
    {
718
        // If the being can't move, just change direction
719
720
//            if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION))
721
        {
722
            playerHandler->setDirection(dir);
723
            setDirection(dir);
724
        }
725
    }
726
}
727
728
void LocalPlayer::stopWalking(const bool sendToServer)
729
{
730
    if (mAction == BeingAction::MOVE && (mWalkingDir != 0U))
731
    {
732
        mWalkingDir = 0;
733
        mPickUpTarget = nullptr;
734
        setDestination(mPixelX,
735
            mPixelY);
736
        if (sendToServer)
737
        {
738
            playerHandler->setDestination(
739
                mPixelX,
740
                mPixelY,
741
                -1);
742
        }
743
        setAction(BeingAction::STAND, 0);
744
    }
745
746
    // No path set anymore, so we reset the path by mouse flag
747
    mPathSetByMouse = false;
748
749
    clearPath();
750
    navigateClean();
751
}
752
753
bool LocalPlayer::toggleSit() const
754
{
755
    if (!PacketLimiter::limitPackets(PacketType::PACKET_SIT))
756
        return false;
757
758
    BeingActionT newAction;
759
    switch (mAction)
760
    {
761
        case BeingAction::STAND:
762
        case BeingAction::PRESTAND:
763
        case BeingAction::SPAWN:
764
            newAction = BeingAction::SIT;
765
            break;
766
        case BeingAction::SIT:
767
            newAction = BeingAction::STAND;
768
            break;
769
        case BeingAction::MOVE:
770
        case BeingAction::ATTACK:
771
        case BeingAction::DEAD:
772
        case BeingAction::HURT:
773
        case BeingAction::CAST:
774
        default:
775
            return true;
776
    }
777
778
    playerHandler->changeAction(newAction);
779
    return true;
780
}
781
782
bool LocalPlayer::updateSit() const
783
{
784
    if (!PacketLimiter::limitPackets(PacketType::PACKET_SIT))
785
        return false;
786
787
    playerHandler->changeAction(mAction);
788
    return true;
789
}
790
791
bool LocalPlayer::emote(const uint8_t emotion)
792
{
793
    if (!PacketLimiter::limitPackets(PacketType::PACKET_EMOTE))
794
        return false;
795
796
    playerHandler->emote(emotion);
797
    return true;
798
}
799
800
void LocalPlayer::attack(Being *const target,
801
                         const bool keep,
802
                         const bool dontChangeEquipment)
803
{
804
    mKeepAttacking = keep;
805
806
    if ((target == nullptr) || target->getType() == ActorType::Npc)
807
        return;
808
809
    if (mTarget != target)
810
        setTarget(target);
811
812
    // Must be standing or sitting or casting to attack
813
    if (mAction != BeingAction::STAND &&
814
        mAction != BeingAction::SIT &&
815
        mAction != BeingAction::CAST)
816
    {
817
        return;
818
    }
819
820
#ifdef TMWA_SUPPORT
821
    const int dist_x = target->mX - mX;
822
    const int dist_y = target->mY - mY;
823
824
    if (Net::getNetworkType() == ServerType::TMWATHENA)
825
    {
826
        if (abs(dist_y) >= abs(dist_x))
827
        {
828
            if (dist_y > 0)
829
                setDirection(BeingDirection::DOWN);
830
            else
831
                setDirection(BeingDirection::UP);
832
        }
833
        else
834
        {
835
            if (dist_x > 0)
836
                setDirection(BeingDirection::RIGHT);
837
            else
838
                setDirection(BeingDirection::LEFT);
839
        }
840
    }
841
#endif  // TMWA_SUPPORT
842
843
    mActionTime = tick_time;
844
845
    if (target->getType() != ActorType::Player
846
        || checAttackPermissions(target))
847
    {
848
        setAction(BeingAction::ATTACK, 0);
849
850
        if (!PacketLimiter::limitPackets(PacketType::PACKET_ATTACK))
851
            return;
852
853
        if (!dontChangeEquipment)
854
            changeEquipmentBeforeAttack(target);
855
856
        const BeingId targetId = target->getId();
857
        playerHandler->attack(targetId, mServerAttack);
858
        PlayerInfo::updateAttackAi(targetId, mServerAttack);
859
    }
860
861
    if (!keep)
862
        stopAttack(false);
863
}
864
865
void LocalPlayer::stopAttack(const bool keepAttack)
866
{
867
    if (!PacketLimiter::limitPackets(PacketType::PACKET_STOPATTACK))
868
        return;
869
870
    if (mServerAttack == Keep_true && mAction == BeingAction::ATTACK)
871
        playerHandler->stopAttack();
872
873
    untarget();
874
    if (!keepAttack || !mAttackNext)
875
        mKeepAttacking = false;
876
}
877
878
void LocalPlayer::untarget()
879
{
880
    if (mAction == BeingAction::ATTACK)
881
        setAction(BeingAction::STAND, 0);
882
883
    if (mTarget != nullptr)
884
        setTarget(nullptr);
885
}
886
887
void LocalPlayer::pickedUp(const ItemInfo &itemInfo,
888
                           const int amount,
889
                           const ItemColor color,
890
                           const BeingId floorItemId,
891
                           const PickupT fail)
892
{
893
    if (fail != Pickup::OKAY)
894
    {
895
        if ((actorManager != nullptr) && floorItemId != BeingId_zero)
896
        {
897
            FloorItem *const item = actorManager->findItem(floorItemId);
898
            if (item != nullptr)
899
            {
900
                if (!item->getShowMsg())
901
                    return;
902
                item->setShowMsg(false);
903
            }
904
        }
905
        const char* msg = nullptr;
906
        switch (fail)
907
        {
908
            case Pickup::BAD_ITEM:
909
                // TRANSLATORS: pickup error message
910
                msg = N_("Tried to pick up nonexistent item.");
911
                break;
912
            case Pickup::TOO_HEAVY:
913
                // TRANSLATORS: pickup error message
914
                msg = N_("Item is too heavy.");
915
                break;
916
            case Pickup::TOO_FAR:
917
                // TRANSLATORS: pickup error message
918
                msg = N_("Item is too far away.");
919
                break;
920
            case Pickup::INV_FULL:
921
                // TRANSLATORS: pickup error message
922
                msg = N_("Inventory is full.");
923
                break;
924
            case Pickup::STACK_FULL:
925
                // TRANSLATORS: pickup error message
926
                msg = N_("Stack is too big.");
927
                break;
928
            case Pickup::DROP_STEAL:
929
                // TRANSLATORS: pickup error message
930
                msg = N_("Item belongs to someone else.");
931
                break;
932
            case Pickup::MAX_AMOUNT:
933
                // TRANSLATORS: pickup error message
934
                msg = N_("You can't pickup this amount of items.");
935
                break;
936
            case Pickup::STACK_AMOUNT:
937
                // TRANSLATORS: pickup error message
938
                msg = N_("Your item stack has max amount.");
939
                break;
940
            case Pickup::OKAY:
941
                break;
942
            default:
943
            case Pickup::UNKNOWN:
944
                // TRANSLATORS: pickup error message
945
                msg = N_("Unknown problem picking up item.");
946
                break;
947
        }
948
        if (localChatTab != nullptr &&
949
            config.getBoolValue("showpickupchat"))
950
        {
951
            localChatTab->chatLog(gettext(msg),
952
                ChatMsgType::BY_SERVER,
953
                IgnoreRecord_false,
954
                TryRemoveColors_true);
955
        }
956
957
        if ((mMap != nullptr) && config.getBoolValue("showpickupparticle"))
958
        {
959
            // Show pickup notification
960
            addMessageToQueue(gettext(msg), UserColorId::PICKUP_INFO);
961
        }
962
    }
963
    else
964
    {
965
        std::string str;
966
#ifdef TMWA_SUPPORT
967
        if (Net::getNetworkType() == ServerType::TMWATHENA)
968
        {
969
            str = itemInfo.getName();
970
        }
971
        else
972
#endif  // TMWA_SUPPORT
973
        {
974
            str = itemInfo.getName(color);
975
        }
976
977
        if (config.getBoolValue("showpickupchat") && (localChatTab != nullptr))
978
        {
979
            // TRANSLATORS: %d is number,
980
            // [@@%d|%s@@] - here player can see link to item
981
            localChatTab->chatLog(strprintf(ngettext("You picked up %d "
982
                "[@@%d|%s@@].", "You picked up %d [@@%d|%s@@].", amount),
983
                amount, itemInfo.getId(), str.c_str()),
984
                ChatMsgType::BY_SERVER,
985
                IgnoreRecord_false,
986
                TryRemoveColors_true);
987
        }
988
989
        if ((mMap != nullptr) && config.getBoolValue("showpickupparticle"))
990
        {
991
            // Show pickup notification
992
            if (amount > 1)
993
            {
994
                addMessageToQueue(strprintf("%d x %s", amount,
995
                    str.c_str()), UserColorId::PICKUP_INFO);
996
            }
997
            else
998
            {
999
                addMessageToQueue(str, UserColorId::PICKUP_INFO);
1000
            }
1001
        }
1002
    }
1003
}
1004
1005
2
int LocalPlayer::getAttackRange() const
1006
{
1007
✗✓
2
    if (mAttackRange > -1)
1008
    {
1009
        return mAttackRange;
1010
    }
1011
1012
    const Item *const weapon = PlayerInfo::getEquipment(
1013
        EquipSlot::FIGHT1_SLOT);
1014
    if (weapon != nullptr)
1015
    {
1016
        const ItemInfo &info = weapon->getInfo();
1017
        return info.getAttackRange();
1018
    }
1019
    return 48;  // unarmed range
1020
}
1021
1022
bool LocalPlayer::withinAttackRange(const Being *const target,
1023
                                    const bool fixDistance,
1024
                                    const int addRange) const
1025
{
1026
    if (target == nullptr)
1027
        return false;
1028
1029
    int range = getAttackRange() + addRange;
1030
    int dx;
1031
    int dy;
1032
1033
    if (fixDistance && range == 1)
1034
        range = 2;
1035
1036
    dx = CAST_S32(abs(target->mX - mX));
1037
    dy = CAST_S32(abs(target->mY - mY));
1038
    return !(dx > range || dy > range);
1039
}
1040
1041
void LocalPlayer::setGotoTarget(Being *const target)
1042
{
1043
    if (target == nullptr)
1044
        return;
1045
1046
    mPickUpTarget = nullptr;
1047
    setTarget(target);
1048
    mGoingToTarget = true;
1049
    navigateTo(target->mX,
1050
        target->mY);
1051
}
1052
1053
void LocalPlayer::handleStatusEffect(const StatusEffect *const effect,
1054
                                     const int32_t effectId,
1055
                                     const Enable newStatus,
1056
                                     const IsStart start)
1057
{
1058
    Being::handleStatusEffect(effect,
1059
        effectId,
1060
        newStatus,
1061
        start);
1062
1063
    if (effect != nullptr)
1064
    {
1065
        effect->deliverMessage();
1066
        effect->playSFX();
1067
1068
        AnimatedSprite *const sprite = effect->getIcon();
1069
1070
        if (sprite == nullptr)
1071
        {
1072
            // delete sprite, if necessary
1073
            for (size_t i = 0; i < mStatusEffectIcons.size(); )
1074
            {
1075
                if (mStatusEffectIcons[i] == effectId)
1076
                {
1077
                    mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i);
1078
                    if (miniStatusWindow != nullptr)
1079
                        miniStatusWindow->eraseIcon(CAST_S32(i));
1080
                }
1081
                else
1082
                {
1083
                    i++;
1084
                }
1085
            }
1086
        }
1087
        else
1088
        {
1089
            // replace sprite or append
1090
            bool found = false;
1091
            const size_t sz = mStatusEffectIcons.size();
1092
            for (size_t i = 0; i < sz; i++)
1093
            {
1094
                if (mStatusEffectIcons[i] == effectId)
1095
                {
1096
                    if (miniStatusWindow != nullptr)
1097
                        miniStatusWindow->setIcon(CAST_S32(i), sprite);
1098
                    found = true;
1099
                    break;
1100
                }
1101
            }
1102
1103
            if (!found)
1104
            {   // add new
1105
                if (miniStatusWindow != nullptr)
1106
                {
1107
                    const int offset = CAST_S32(mStatusEffectIcons.size());
1108
                    miniStatusWindow->setIcon(offset, sprite);
1109
                }
1110
                mStatusEffectIcons.push_back(effectId);
1111
            }
1112
        }
1113
    }
1114
}
1115
1116
void LocalPlayer::addMessageToQueue(const std::string &message,
1117
                                    const UserColorIdT color)
1118
{
1119
    if (mMessages.size() < 20)
1120
        mMessages.push_back(MessagePair(message, color));
1121
}
1122
1123
void LocalPlayer::optionChanged(const std::string &value)
1124
{
1125
    if (value == "showownname")
1126
    {
1127
        setShowName(config.getBoolValue("showownname"));
1128
    }
1129
    else if (value == "targetDeadPlayers")
1130
    {
1131
        mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers");
1132
    }
1133
    else if (value == "enableBuggyServers")
1134
    {
1135
        mIsServerBuggy = serverConfig.getBoolValue("enableBuggyServers");
1136
    }
1137
    else if (value == "syncPlayerMove")
1138
    {
1139
        mSyncPlayerMove = config.getBoolValue("syncPlayerMove");
1140
    }
1141
    else if (value == "syncPlayerMoveDistance")
1142
    {
1143
#ifdef TMWA_SUPPORT
1144
        if (Net::getNetworkType() != ServerType::TMWATHENA)
1145
#endif
1146
        {
1147
            mSyncPlayerMoveDistance =
1148
                config.getIntValue("syncPlayerMoveDistance");
1149
        }
1150
    }
1151
#ifdef TMWA_SUPPORT
1152
    else if (value == "syncPlayerMoveDistanceLegacy")
1153
    {
1154
        if (Net::getNetworkType() == ServerType::TMWATHENA)
1155
        {
1156
            mSyncPlayerMoveDistance =
1157
                config.getIntValue("syncPlayerMoveDistanceLegacy");
1158
        }
1159
    }
1160
#endif
1161
    else if (value == "drawPath")
1162
    {
1163
        mDrawPath = config.getBoolValue("drawPath");
1164
    }
1165
    else if (value == "serverAttack")
1166
    {
1167
        mServerAttack = fromBool(config.getBoolValue("serverAttack"), Keep);
1168
    }
1169
    else if (value == "attackMoving")
1170
    {
1171
        mAttackMoving = config.getBoolValue("attackMoving");
1172
    }
1173
    else if (value == "attackNext")
1174
    {
1175
        mAttackNext = config.getBoolValue("attackNext");
1176
    }
1177
    else if (value == "showJobExp")
1178
    {
1179
        mShowJobExp = config.getBoolValue("showJobExp");
1180
    }
1181
    else if (value == "enableAdvert")
1182
    {
1183
        mEnableAdvert = config.getBoolValue("enableAdvert");
1184
    }
1185
    else if (value == "tradebot")
1186
    {
1187
        mTradebot = config.getBoolValue("tradebot");
1188
    }
1189
    else if (value == "targetOnlyReachable")
1190
    {
1191
        mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable");
1192
    }
1193
    else if (value == "showserverpos")
1194
    {
1195
        mShowServerPos = config.getBoolValue("showserverpos");
1196
    }
1197
    else if (value == "visiblenames")
1198
    {
1199
        mVisibleNames = static_cast<VisibleName::Type>(
1200
            config.getIntValue("visiblenames"));
1201
    }
1202
}
1203
1204
void LocalPlayer::addJobMessage(const int64_t change)
1205
{
1206
    if (change != 0 && mMessages.size() < 20)
1207
    {
1208
        const std::string xpStr = toString(CAST_U64(change));
1209
        if (!mMessages.empty())
1210
        {
1211
            MessagePair pair = mMessages.back();
1212
            // TRANSLATORS: this is normal experience
1213
            if (pair.first.find(strprintf(" %s", _("xp"))) ==
1214
                // TRANSLATORS: this is normal experience
1215
                pair.first.size() - strlen(_("xp")) - 1)
1216
            {
1217
                mMessages.pop_back();
1218
                pair.first.append(strprintf(", %s %s",
1219
                    xpStr.c_str(),
1220
                    // TRANSLATORS: this is job experience
1221
                    _("job")));
1222
                mMessages.push_back(pair);
1223
            }
1224
            else
1225
            {
1226
                addMessageToQueue(strprintf("%s %s",
1227
                    xpStr.c_str(),
1228
                    // TRANSLATORS: this is job experience
1229
                    _("job")),
1230
                    UserColorId::EXP_INFO);
1231
            }
1232
        }
1233
        else
1234
        {
1235
            addMessageToQueue(strprintf("%s %s",
1236
                xpStr.c_str(),
1237
                // TRANSLATORS: this is job experience
1238
                _("job")),
1239
                UserColorId::EXP_INFO);
1240
        }
1241
    }
1242
}
1243
1244
void LocalPlayer::addXpMessage(const int64_t change)
1245
{
1246
    if (change != 0 && mMessages.size() < 20)
1247
    {
1248
        addMessageToQueue(strprintf("%s %s",
1249
            toString(CAST_U64(change)).c_str(),
1250
            // TRANSLATORS: get xp message
1251
            _("xp")),
1252
            UserColorId::EXP_INFO);
1253
    }
1254
}
1255
1256
void LocalPlayer::addHomunXpMessage(const int change)
1257
{
1258
    if (change != 0 && mMessages.size() < 20)
1259
    {
1260
        addMessageToQueue(strprintf("%s %d %s",
1261
            // TRANSLATORS: get homunculus xp message
1262
            _("Homun"),
1263
            change,
1264
            // TRANSLATORS: get xp message
1265
            _("xp")),
1266
            UserColorId::EXP_INFO);
1267
    }
1268
}
1269
1270
void LocalPlayer::addHpMessage(const int change)
1271
{
1272
    if (change != 0 && mMessages.size() < 20)
1273
    {
1274
        // TRANSLATORS: get hp message
1275
        addMessageToQueue(strprintf("%d %s", change, _("hp")),
1276
            UserColorId::EXP_INFO);
1277
    }
1278
}
1279
1280
void LocalPlayer::addSpMessage(const int change)
1281
{
1282
    if (change != 0 && mMessages.size() < 20)
1283
    {
1284
        // TRANSLATORS: get hp message
1285
        addMessageToQueue(strprintf("%d %s", change, _("mana")),
1286
            UserColorId::EXP_INFO);
1287
    }
1288
}
1289
1290
void LocalPlayer::attributeChanged(const AttributesT id,
1291
                                   const int64_t oldVal,
1292
                                   const int64_t newVal)
1293
{
1294
    PRAGMA45(GCC diagnostic push)
1295
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
1296
    switch (id)
1297
    {
1298
        case Attributes::PLAYER_EXP:
1299
        {
1300
            if (Net::getNetworkType() != ServerType::TMWATHENA)
1301
                break;
1302
            if (oldVal > newVal)
1303
                break;
1304
1305
            const int change = CAST_S32(newVal - oldVal);
1306
            addXpMessage(change);
1307
            break;
1308
        }
1309
        case Attributes::PLAYER_BASE_LEVEL:
1310
            mLevel = CAST_S32(newVal);
1311
            break;
1312
        case Attributes::PLAYER_HP:
1313
            if (oldVal != 0 && newVal == 0)
1314
                PlayerDeathListener::distributeEvent();
1315
            break;
1316
        case Attributes::PLAYER_JOB_EXP:
1317
        {
1318
            if (!mShowJobExp ||
1319
                Net::getNetworkType() != ServerType::TMWATHENA)
1320
            {
1321
                return;
1322
            }
1323
            if (oldVal > newVal ||
1324
                PlayerInfo::getAttribute(
1325
                Attributes::PLAYER_JOB_EXP_NEEDED) == 0)
1326
            {
1327
                return;
1328
            }
1329
            const int32_t change = CAST_S32(newVal - oldVal);
1330
            addJobMessage(change);
1331
            break;
1332
        }
1333
        default:
1334
            break;
1335
    }
1336
    PRAGMA45(GCC diagnostic pop)
1337
}
1338
1339
void LocalPlayer::move(const int dX, const int dY)
1340
{
1341
    mPickUpTarget = nullptr;
1342
    setDestination(mX + dX, mY + dY);
1343
}
1344
1345
void LocalPlayer::moveToTarget(int dist)
1346
{
1347
    bool gotPos(false);
1348
    Path debugPath;
1349
1350
    size_t limit(0);
1351
1352
    if (dist == -1)
1353
    {
1354
        dist = settings.moveToTargetType;
1355
        if (dist != 0)
1356
        {
1357
            const bool broken = (Net::getNetworkType() ==
1358
                ServerType::TMWATHENA);
1359
            switch (dist)
1360
            {
1361
                case 10:
1362
                    dist = mAttackRange;
1363
                    if (dist == 1 && broken)
1364
                        dist = 2;
1365
                    break;
1366
                case 11:
1367
                    dist = mAttackRange - 1;
1368
                    if (dist < 1)
1369
                        dist = 1;
1370
                    if (dist == 1 && broken)
1371
                        dist = 2;
1372
                    break;
1373
                default:
1374
                    break;
1375
            }
1376
        }
1377
    }
1378
1379
    if (mTarget != nullptr)
1380
    {
1381
        if (mMap != nullptr)
1382
        {
1383
            debugPath = mMap->findPath(
1384
                (mPixelX - mapTileSize / 2) / mapTileSize,
1385
                (mPixelY - mapTileSize) / mapTileSize,
1386
                mTarget->mX,
1387
                mTarget->mY,
1388
                getBlockWalkMask(),
1389
                0);
1390
        }
1391
1392
        const size_t sz = debugPath.size();
1393
        if (sz < CAST_SIZE(dist))
1394
            return;
1395
        limit = CAST_S32(sz) - dist;
1396
        gotPos = true;
1397
    }
1398
    else if ((mNavigateX != 0) || (mNavigateY != 0))
1399
    {
1400
        debugPath = mNavigatePath;
1401
        limit = dist;
1402
        gotPos = true;
1403
    }
1404
1405
    if (gotPos)
1406
    {
1407
        if (dist == 0)
1408
        {
1409
            if (mTarget != nullptr)
1410
                navigateTo(mTarget->mX, mTarget->mY);
1411
        }
1412
        else
1413
        {
1414
            Position pos(0, 0);
1415
            size_t f = 0;
1416
1417
            for (Path::const_iterator i = debugPath.begin(),
1418
                 i_fend = debugPath.end();
1419
                 i != i_fend && f < limit; ++i, f++)
1420
            {
1421
                pos = (*i);
1422
            }
1423
            navigateTo(pos.x, pos.y);
1424
        }
1425
    }
1426
    else if ((mLastTargetX != 0) || (mLastTargetY != 0))
1427
    {
1428
        navigateTo(mLastTargetX, mLastTargetY);
1429
    }
1430
}
1431
1432
void LocalPlayer::moveToHome()
1433
{
1434
    mPickUpTarget = nullptr;
1435
    if ((mX != mCrossX || mY != mCrossY) && (mCrossX != 0) && (mCrossY != 0))
1436
    {
1437
        setDestination(mCrossX, mCrossY);
1438
    }
1439
    else if (mMap != nullptr)
1440
    {
1441
        const std::map<std::string, Vector>::const_iterator iter =
1442
            mHomes.find(mMap->getProperty("_realfilename", std::string()));
1443
1444
        if (iter != mHomes.end())
1445
        {
1446
            const Vector pos = mHomes[(*iter).first];
1447
            if (mX == pos.x && mY == pos.y)
1448
            {
1449
                playerHandler->setDestination(
1450
                        CAST_S32(pos.x),
1451
                        CAST_S32(pos.y),
1452
                        CAST_S32(mDirection));
1453
            }
1454
            else
1455
            {
1456
                navigateTo(CAST_S32(pos.x), CAST_S32(pos.y));
1457
            }
1458
        }
1459
    }
1460
}
1461
1462
void LocalPlayer::changeEquipmentBeforeAttack(const Being *const target) const
1463
{
1464
    if (settings.attackWeaponType == 1
1465
        || (target == nullptr)
1466
        || (PlayerInfo::getInventory() == nullptr))
1467
    {
1468
        return;
1469
    }
1470
1471
    bool allowSword = false;
1472
    const int dx = target->mX - mX;
1473
    const int dy = target->mY - mY;
1474
    const Item *item = nullptr;
1475
1476
    if (dx * dx + dy * dy > 80)
1477
        return;
1478
1479
    if (dx * dx + dy * dy < 8)
1480
        allowSword = true;
1481
1482
    const Inventory *const inv = PlayerInfo::getInventory();
1483
    if (inv == nullptr)
1484
        return;
1485
1486
    // if attack distance for sword
1487
    if (allowSword)
1488
    {
1489
        // searching swords
1490
        const WeaponsInfos &swords = WeaponsDB::getSwords();
1491
        FOR_EACH (WeaponsInfosIter, it, swords)
1492
        {
1493
            item = inv->findItem(*it, ItemColor_zero);
1494
            if (item != nullptr)
1495
                break;
1496
        }
1497
1498
        // no swords
1499
        if (item == nullptr)
1500
            return;
1501
1502
        // if sword not equiped
1503
        if (item->isEquipped() == Equipped_false)
1504
            PlayerInfo::equipItem(item, Sfx_true);
1505
1506
        // if need equip shield too
1507
        if (settings.attackWeaponType == 3)
1508
        {
1509
            // searching shield
1510
            const WeaponsInfos &shields = WeaponsDB::getShields();
1511
            FOR_EACH (WeaponsInfosIter, it, shields)
1512
            {
1513
                item = inv->findItem(*it, ItemColor_zero);
1514
                if (item != nullptr)
1515
                    break;
1516
            }
1517
            if ((item != nullptr) && item->isEquipped() == Equipped_false)
1518
                PlayerInfo::equipItem(item, Sfx_true);
1519
        }
1520
    }
1521
    // big distance. allowed only bow
1522
    else
1523
    {
1524
        // searching bow
1525
        const WeaponsInfos &bows = WeaponsDB::getBows();
1526
        FOR_EACH (WeaponsInfosIter, it, bows)
1527
        {
1528
            item = inv->findItem(*it, ItemColor_zero);
1529
            if (item != nullptr)
1530
                break;
1531
        }
1532
1533
        // no bow
1534
        if (item == nullptr)
1535
            return;
1536
1537
        if (item->isEquipped() == Equipped_false)
1538
            PlayerInfo::equipItem(item, Sfx_true);
1539
    }
1540
}
1541
1542
bool LocalPlayer::isReachable(Being *const being,
1543
                              const int maxCost)
1544
{
1545
    if ((being == nullptr) || (mMap == nullptr))
1546
        return false;
1547
1548
    if (being->getReachable() == Reachable::REACH_NO)
1549
        return false;
1550
1551
    if (being->mX == mX &&
1552
        being->mY == mY)
1553
    {
1554
        being->setDistance(0);
1555
        being->setReachable(Reachable::REACH_YES);
1556
        return true;
1557
    }
1558
    else if (being->mX - 1 <= mX &&
1559
             being->mX + 1 >= mX &&
1560
             being->mY - 1 <= mY &&
1561
             being->mY + 1 >= mY)
1562
    {
1563
        being->setDistance(1);
1564
        being->setReachable(Reachable::REACH_YES);
1565
        return true;
1566
    }
1567
1568
    const Path debugPath = mMap->findPath(
1569
        (mPixelX - mapTileSize / 2) / mapTileSize,
1570
        (mPixelY - mapTileSize) / mapTileSize,
1571
        being->mX,
1572
        being->mY,
1573
        getBlockWalkMask(),
1574
        maxCost);
1575
1576
    being->setDistance(CAST_S32(debugPath.size()));
1577
    if (!debugPath.empty())
1578
    {
1579
        being->setReachable(Reachable::REACH_YES);
1580
        return true;
1581
    }
1582
    being->setReachable(Reachable::REACH_NO);
1583
    return false;
1584
}
1585
1586
bool LocalPlayer::isReachable(const int x, const int y,
1587
                              const bool allowCollision) const
1588
{
1589
    const WalkLayer *const walk = mMap->getWalkLayer();
1590
    if (walk == nullptr)
1591
        return false;
1592
    int num = walk->getDataAt(x, y);
1593
    if (allowCollision && num < 0)
1594
        num = -num;
1595
1596
    return walk->getDataAt(mX, mY) == num;
1597
}
1598
1599
bool LocalPlayer::pickUpItems(int pickUpType)
1600
{
1601
    if (actorManager == nullptr)
1602
        return false;
1603
1604
    bool status = false;
1605
    int x = mX;
1606
    int y = mY;
1607
1608
    // first pick up item on player position
1609
    FloorItem *item =
1610
        actorManager->findItem(x, y);
1611
    if (item != nullptr)
1612
        status = pickUp(item);
1613
1614
    if (pickUpType == 0)
1615
        pickUpType = settings.pickUpType;
1616
1617
    if (pickUpType == 0)
1618
        return status;
1619
1620
    int x1;
1621
    int y1;
1622
    int x2;
1623
    int y2;
1624
    switch (pickUpType)
1625
    {
1626
        case 1:
1627
            switch (mDirection)
1628
            {
1629
                case BeingDirection::UP   : --y; break;
1630
                case BeingDirection::DOWN : ++y; break;
1631
                case BeingDirection::LEFT : --x; break;
1632
                case BeingDirection::RIGHT: ++x; break;
1633
                default: break;
1634
            }
1635
            item = actorManager->findItem(x, y);
1636
            if (item != nullptr)
1637
                status = pickUp(item);
1638
            break;
1639
        case 2:
1640
            switch (mDirection)
1641
            {
1642
                case BeingDirection::UP:
1643
                    x1 = x - 1; y1 = y - 1; x2 = x + 1; y2 = y; break;
1644
                case BeingDirection::DOWN:
1645
                    x1 = x - 1; y1 = y; x2 = x + 1; y2 = y + 1; break;
1646
                case BeingDirection::LEFT:
1647
                    x1 = x - 1; y1 = y - 1; x2 = x; y2 = y + 1; break;
1648
                case BeingDirection::RIGHT:
1649
                    x1 = x; y1 = y - 1; x2 = x + 1; y2 = y + 1; break;
1650
                default:
1651
                    x1 = x; x2 = x; y1 = y; y2 = y; break;
1652
            }
1653
            if (actorManager->pickUpAll(x1, y1, x2, y2, false))
1654
                status = true;
1655
            break;
1656
        case 3:
1657
            if (actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1658
                status = true;
1659
            break;
1660
1661
        case 4:
1662
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1663
            {
1664
                if (actorManager->pickUpNearest(x, y, 4))
1665
                    status = true;
1666
            }
1667
            else
1668
            {
1669
                status = true;
1670
            }
1671
            break;
1672
1673
        case 5:
1674
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1675
            {
1676
                if (actorManager->pickUpNearest(x, y, 8))
1677
                    status = true;
1678
            }
1679
            else
1680
            {
1681
                status = true;
1682
            }
1683
            break;
1684
1685
        case 6:
1686
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1687
            {
1688
                if (actorManager->pickUpNearest(x, y, 90))
1689
                    status = true;
1690
            }
1691
            else
1692
            {
1693
                status = true;
1694
            }
1695
            break;
1696
1697
        default:
1698
            break;
1699
    }
1700
    return status;
1701
}
1702
1703
1704
void LocalPlayer::moveByDirection(const unsigned char dir)
1705
{
1706
    int dx = 0;
1707
    int dy = 0;
1708
    if ((dir & BeingDirection::UP) != 0)
1709
        dy--;
1710
    if ((dir & BeingDirection::DOWN) != 0)
1711
        dy++;
1712
    if ((dir & BeingDirection::LEFT) != 0)
1713
        dx--;
1714
    if ((dir & BeingDirection::RIGHT) != 0)
1715
        dx++;
1716
    move(dx, dy);
1717
}
1718
1719
void LocalPlayer::specialMove(const unsigned char direction)
1720
{
1721
    if ((direction != 0U) && ((mNavigateX != 0) || (mNavigateY != 0)))
1722
        navigateClean();
1723
1724
    if ((direction != 0U) && (settings.moveType >= 2
1725
        && settings.moveType <= 4))
1726
    {
1727
        if (mAction == BeingAction::MOVE)
1728
            return;
1729
1730
        unsigned int max;
1731
1732
        if (settings.moveType == 2)
1733
            max = 5;
1734
        else if (settings.moveType == 4)
1735
            max = 1;
1736
        else
1737
            max = 3;
1738
1739
        if (getMoveState() < max)
1740
        {
1741
            moveByDirection(direction);
1742
            mMoveState ++;
1743
        }
1744
        else
1745
        {
1746
            mMoveState = 0;
1747
            crazyMoves->crazyMove();
1748
        }
1749
    }
1750
    else
1751
    {
1752
        setWalkingDir(direction);
1753
    }
1754
}
1755
1756
#ifdef TMWA_SUPPORT
1757
void LocalPlayer::magicAttack() const
1758
{
1759
    if (Net::getNetworkType() != ServerType::TMWATHENA)
1760
        return;
1761
    if (chatWindow == nullptr ||
1762
        !isAlive() ||
1763
        !playerHandler->canUseMagic())
1764
    {
1765
        return;
1766
    }
1767
1768
    switch (settings.magicAttackType)
1769
    {
1770
        // flar W00
1771
        case 0:
1772
            tryMagic("#flar", 1, 0, 10);
1773
            break;
1774
        // chiza W01
1775
        case 1:
1776
            tryMagic("#chiza", 1, 0,  9);
1777
            break;
1778
        // ingrav W10
1779
        case 2:
1780
            tryMagic("#ingrav", 2, 2,  20);
1781
            break;
1782
        // frillyar W11
1783
        case 3:
1784
            tryMagic("#frillyar", 2, 2, 25);
1785
            break;
1786
        // upmarmu W12
1787
        case 4:
1788
            tryMagic("#upmarmu", 2, 2, 20);
1789
            break;
1790
        default:
1791
            break;
1792
    }
1793
}
1794
1795
void LocalPlayer::tryMagic(const std::string &spell, const int baseMagic,
1796
                           const int schoolMagic, const int mana)
1797
{
1798
    if (chatWindow == nullptr)
1799
        return;
1800
1801
    if (PlayerInfo::getSkillLevel(340) >= baseMagic
1802
        && PlayerInfo::getSkillLevel(342) >= schoolMagic)
1803
    {
1804
        if (PlayerInfo::getAttribute(Attributes::PLAYER_MP) >= mana)
1805
        {
1806
            if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
1807
                return;
1808
1809
            chatWindow->localChatInput(spell);
1810
        }
1811
    }
1812
}
1813
#endif  // TMWA_SUPPORT
1814
1815
94
void LocalPlayer::loadHomes()
1816
{
1817
188
    std::string buf;
1818
✓✗✓✗
✓✗✓✗
846
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1819
1820
✓✗✗✓
188
    while (ss >> buf)
1821
    {
1822
        Vector pos;
1823
        ss >> pos.x;
1824
        ss >> pos.y;
1825
        mHomes[buf] = pos;
1826
    }
1827
94
}
1828
1829
94
void LocalPlayer::setMap(Map *const map)
1830
{
1831
    BLOCK_START("LocalPlayer::setMap")
1832
✗✓
94
    if (map != nullptr)
1833
    {
1834
        if (socialWindow != nullptr)
1835
            socialWindow->updateActiveList();
1836
    }
1837
94
    navigateClean();
1838
94
    mCrossX = 0;
1839
94
    mCrossY = 0;
1840
1841
94
    Being::setMap(map);
1842
94
    updateNavigateList();
1843
    BLOCK_END("LocalPlayer::setMap")
1844
94
}
1845
1846
void LocalPlayer::setHome()
1847
{
1848
    if ((mMap == nullptr) || (socialWindow == nullptr))
1849
        return;
1850
1851
    SpecialLayer *const specialLayer = mMap->getSpecialLayer();
1852
1853
    if (specialLayer == nullptr)
1854
        return;
1855
1856
    const std::string key = mMap->getProperty("_realfilename", std::string());
1857
    Vector pos = mHomes[key];
1858
1859
    if (mAction == BeingAction::SIT)
1860
    {
1861
        const std::map<std::string, Vector>::const_iterator
1862
            iter = mHomes.find(key);
1863
1864
        if (iter != mHomes.end())
1865
        {
1866
            socialWindow->removePortal(CAST_S32(pos.x),
1867
                CAST_S32(pos.y));
1868
        }
1869
1870
        if (iter != mHomes.end() && mX == CAST_S32(pos.x)
1871
            && mY == CAST_S32(pos.y))
1872
        {
1873
            mMap->updatePortalTile("",
1874
                MapItemType::EMPTY,
1875
                CAST_S32(pos.x),
1876
                CAST_S32(pos.y),
1877
                true);
1878
1879
            mHomes.erase(key);
1880
            socialWindow->removePortal(CAST_S32(pos.x),
1881
                CAST_S32(pos.y));
1882
        }
1883
        else
1884
        {
1885
            if (iter != mHomes.end())
1886
            {
1887
                specialLayer->setTile(CAST_S32(pos.x),
1888
                    CAST_S32(pos.y), MapItemType::EMPTY);
1889
                specialLayer->updateCache();
1890
            }
1891
1892
            pos.x = static_cast<float>(mX);
1893
            pos.y = static_cast<float>(mY);
1894
            mHomes[key] = pos;
1895
            mMap->updatePortalTile("home",
1896
                MapItemType::HOME,
1897
                mX,
1898
                mY,
1899
                true);
1900
            socialWindow->addPortal(mX, mY);
1901
        }
1902
        MapItem *const mapItem = specialLayer->getTile(mX, mY);
1903
        if (mapItem != nullptr)
1904
        {
1905
            const int idx = socialWindow->getPortalIndex(mX, mY);
1906
            mapItem->setName(KeyboardConfig::getKeyShortString(
1907
                OutfitWindow::keyName(idx)));
1908
        }
1909
        saveHomes();
1910
    }
1911
    else
1912
    {
1913
        MapItem *mapItem = specialLayer->getTile(mX, mY);
1914
        int type = 0;
1915
1916
        const std::map<std::string, Vector>::iterator iter = mHomes.find(key);
1917
        if (iter != mHomes.end() && mX == pos.x && mY == pos.y)
1918
        {
1919
            mHomes.erase(key);
1920
            saveHomes();
1921
        }
1922
1923
        if ((mapItem == nullptr) || mapItem->getType() == MapItemType::EMPTY)
1924
        {
1925
            if ((mDirection & BeingDirection::UP) != 0)
1926
                type = MapItemType::ARROW_UP;
1927
            else if ((mDirection & BeingDirection::LEFT) != 0)
1928
                type = MapItemType::ARROW_LEFT;
1929
            else if ((mDirection & BeingDirection::DOWN) != 0)
1930
                type = MapItemType::ARROW_DOWN;
1931
            else if ((mDirection & BeingDirection::RIGHT) != 0)
1932
                type = MapItemType::ARROW_RIGHT;
1933
        }
1934
        else
1935
        {
1936
            type = MapItemType::EMPTY;
1937
        }
1938
        mMap->updatePortalTile("",
1939
            type,
1940
            mX,
1941
            mY,
1942
            true);
1943
1944
        if (type != MapItemType::EMPTY)
1945
        {
1946
            socialWindow->addPortal(mX, mY);
1947
            mapItem = specialLayer->getTile(mX, mY);
1948
            if (mapItem != nullptr)
1949
            {
1950
                const int idx = socialWindow->getPortalIndex(mX, mY);
1951
                mapItem->setName(KeyboardConfig::getKeyShortString(
1952
                    OutfitWindow::keyName(idx)));
1953
            }
1954
        }
1955
        else
1956
        {
1957
            specialLayer->setTile(mX, mY, MapItemType::EMPTY);
1958
            specialLayer->updateCache();
1959
            socialWindow->removePortal(mX, mY);
1960
        }
1961
    }
1962
}
1963
1964
void LocalPlayer::saveHomes()
1965
{
1966
    std::stringstream ss;
1967
1968
    for (std::map<std::string, Vector>::const_iterator iter = mHomes.begin(),
1969
         iter_fend = mHomes.end();
1970
         iter != iter_fend;
1971
         ++iter)
1972
    {
1973
        const Vector &pos = (*iter).second;
1974
1975
        if (iter != mHomes.begin())
1976
            ss << " ";
1977
        ss << (*iter).first << " " << pos.x << " " << pos.y;
1978
    }
1979
1980
    serverConfig.setValue("playerHomes", ss.str());
1981
}
1982
1983
void LocalPlayer::pingRequest()
1984
{
1985
    const int time = tick_time;
1986
    if (mWaitPing == true && mPingSendTick != 0)
1987
    {
1988
        if (time >= mPingSendTick && (time - mPingSendTick) > 1000)
1989
            return;
1990
    }
1991
1992
    mPingSendTick = time;
1993
    mWaitPing = true;
1994
    beingHandler->requestNameById(getId());
1995
}
1996
1997
std::string LocalPlayer::getPingTime() const
1998
{
1999
    std::string str;
2000
    if (!mWaitPing)
2001
    {
2002
        if (mPingTime == 0)
2003
            str = "?";
2004
        else
2005
            str = toString(CAST_S32(mPingTime));
2006
    }
2007
    else
2008
    {
2009
        time_t time = tick_time;
2010
        if (time > mPingSendTick)
2011
            time -= mPingSendTick;
2012
        else
2013
            time += MAX_TICK_VALUE - mPingSendTick;
2014
        if (time <= mPingTime)
2015
            time = mPingTime;
2016
        if (mPingTime != time)
2017
            str = strprintf("%d (%d)", CAST_S32(mPingTime), CAST_S32(time));
2018
        else
2019
            str = toString(CAST_S32(time));
2020
    }
2021
    return str;
2022
}
2023
2024
void LocalPlayer::pingResponse()
2025
{
2026
    if (mWaitPing == true && mPingSendTick > 0)
2027
    {
2028
        mWaitPing = false;
2029
        const int time = tick_time;
2030
        if (time < mPingSendTick)
2031
        {
2032
            mPingSendTick = 0;
2033
            mPingTime = 0;
2034
        }
2035
        else
2036
        {
2037
            mPingTime = (time - mPingSendTick) * 10;
2038
        }
2039
    }
2040
}
2041
2042
void LocalPlayer::tryPingRequest()
2043
{
2044
    if (mPingSendTick == 0 || tick_time < mPingSendTick
2045
        || (tick_time - mPingSendTick) > 200)
2046
    {
2047
        pingRequest();
2048
    }
2049
}
2050
2051
2052
void LocalPlayer::setAway(const std::string &message) const
2053
{
2054
    setAfkMessage(message);
2055
    GameModifiers::changeAwayMode(true);
2056
    updateStatus();
2057
}
2058
2059
void LocalPlayer::setAfkMessage(std::string message)
2060
{
2061
    if (!message.empty())
2062
    {
2063
        if (message.size() > 4 && message.substr(0, 4) == "/me ")
2064
        {
2065
            message = message.substr(4);
2066
            config.setValue("afkFormat", 1);
2067
        }
2068
        else
2069
        {
2070
            config.setValue("afkFormat", 0);
2071
        }
2072
        serverConfig.setValue("afkMessage", message);
2073
    }
2074
}
2075
2076
void LocalPlayer::setPseudoAway(const std::string &message)
2077
{
2078
    setAfkMessage(message);
2079
    settings.pseudoAwayMode = !settings.pseudoAwayMode;
2080
}
2081
2082
void LocalPlayer::afkRespond(ChatTab *const tab, const std::string &nick)
2083
{
2084
    if (settings.awayMode)
2085
    {
2086
        const time_t time = cur_time;
2087
        if (mAfkTime == 0 || time < mAfkTime
2088
            || time - mAfkTime > awayLimitTimer)
2089
        {
2090
            std::string str(serverConfig.getValue("afkMessage",
2091
                "I am away from keyboard."));
2092
            if (str.find("'NAME'") != std::string::npos)
2093
                replaceAll(str, "'NAME'", nick);
2094
2095
            std::string msg("*AFK*: " + str);
2096
2097
            if (config.getIntValue("afkFormat") == 1)
2098
                msg = "*" + msg + "*";
2099
2100
            if (tab == nullptr)
2101
            {
2102
                chatHandler->privateMessage(nick, msg);
2103
                if (localChatTab != nullptr)
2104
                {
2105
                    localChatTab->chatLog(std::string(mName).append(
2106
                        " : ").append(msg),
2107
                        ChatMsgType::ACT_WHISPER,
2108
                        IgnoreRecord_false,
2109
                        TryRemoveColors_true);
2110
                }
2111
            }
2112
            else
2113
            {
2114
                if (tab->getNoAway())
2115
                    return;
2116
                chatHandler->privateMessage(nick, msg);
2117
                tab->chatLog(mName, msg);
2118
            }
2119
            mAfkTime = time;
2120
        }
2121
    }
2122
}
2123
2124
bool LocalPlayer::navigateTo(const int x, const int y)
2125
{
2126
    if (mMap == nullptr)
2127
        return false;
2128
2129
    SpecialLayer *const tmpLayer = mMap->getTempLayer();
2130
    if (tmpLayer == nullptr)
2131
        return false;
2132
2133
    mShowNavigePath = true;
2134
    mOldX = mPixelX;
2135
    mOldY = mPixelY;
2136
    mOldTileX = mX;
2137
    mOldTileY = mY;
2138
    mNavigateX = x;
2139
    mNavigateY = y;
2140
    mNavigateId = BeingId_zero;
2141
2142
    mNavigatePath = mMap->findPath(
2143
        (mPixelX - mapTileSize / 2) / mapTileSize,
2144
        (mPixelY - mapTileSize) / mapTileSize,
2145
        x,
2146
        y,
2147
        getBlockWalkMask(),
2148
        0);
2149
2150
    if (mDrawPath)
2151
        tmpLayer->addRoad(mNavigatePath);
2152
    return !mNavigatePath.empty();
2153
}
2154
2155
188
void LocalPlayer::navigateClean()
2156
{
2157
✗✓
188
    if (mMap == nullptr)
2158
        return;
2159
2160
    mShowNavigePath = false;
2161
    mOldX = 0;
2162
    mOldY = 0;
2163
    mOldTileX = 0;
2164
    mOldTileY = 0;
2165
    mNavigateX = 0;
2166
    mNavigateY = 0;
2167
    mNavigateId = BeingId_zero;
2168
2169
    mNavigatePath.clear();
2170
2171
    SpecialLayer *const tmpLayer = mMap->getTempLayer();
2172
    if (tmpLayer == nullptr)
2173
        return;
2174
2175
    tmpLayer->clean();
2176
}
2177
2178
void LocalPlayer::updateMusic() const
2179
{
2180
    if (mMap != nullptr)
2181
    {
2182
        std::string str = mMap->getObjectData(mX, mY, MapItemType::MUSIC);
2183
        if (str.empty())
2184
            str = mMap->getMusicFile();
2185
        if (str != soundManager.getCurrentMusicFile())
2186
        {
2187
            if (str.empty())
2188
                soundManager.fadeOutMusic(1000);
2189
            else
2190
                soundManager.fadeOutAndPlayMusic(str, 1000);
2191
        }
2192
    }
2193
}
2194
2195
void LocalPlayer::updateCoords()
2196
{
2197
    Being::updateCoords();
2198
2199
    // probably map not loaded.
2200
    if ((mPixelX == 0) || (mPixelY == 0))
2201
        return;
2202
2203
    if (mX != mOldTileX || mY != mOldTileY)
2204
    {
2205
        if (socialWindow != nullptr)
2206
            socialWindow->updatePortals();
2207
        PopupManager::hideBeingPopup();
2208
        updateMusic();
2209
    }
2210
2211
    if ((mMap != nullptr) && (mX != mOldTileX || mY != mOldTileY))
2212
    {
2213
        SpecialLayer *const tmpLayer = mMap->getTempLayer();
2214
        if (tmpLayer == nullptr)
2215
            return;
2216
2217
        const int x = (mPixelX - mapTileSize / 2) / mapTileSize;
2218
        const int y = (mPixelY - mapTileSize) / mapTileSize;
2219
        if (mNavigateId != BeingId_zero)
2220
        {
2221
            if (actorManager == nullptr)
2222
            {
2223
                navigateClean();
2224
                return;
2225
            }
2226
2227
            const Being *const being = actorManager
2228
                ->findBeing(mNavigateId);
2229
            if (being == nullptr)
2230
            {
2231
                navigateClean();
2232
                return;
2233
            }
2234
            mNavigateX = being->mX;
2235
            mNavigateY = being->mY;
2236
        }
2237
2238
        if (mNavigateX == x && mNavigateY == y)
2239
        {
2240
            navigateClean();
2241
            return;
2242
        }
2243
        for (Path::const_iterator i = mNavigatePath.begin(),
2244
             i_fend = mNavigatePath.end();
2245
             i != i_fend;
2246
             ++i)
2247
        {
2248
            if ((*i).x == mX && (*i).y == mY)
2249
            {
2250
                mNavigatePath.pop_front();
2251
                fixPos();
2252
                break;
2253
            }
2254
        }
2255
        if (mDrawPath && mShowNavigePath)
2256
        {
2257
            tmpLayer->clean();
2258
            tmpLayer->addRoad(mNavigatePath);
2259
        }
2260
    }
2261
    mOldX = mPixelX;
2262
    mOldY = mPixelY;
2263
    mOldTileX = mX;
2264
    mOldTileY = mY;
2265
}
2266
2267
void LocalPlayer::targetMoved() const
2268
{
2269
/*
2270
    if (mKeepAttacking)
2271
    {
2272
        if (mTarget && mServerAttack == Keep_true)
2273
        {
2274
            logger->log("LocalPlayer::targetMoved0");
2275
            if (!PacketLimiter::limitPackets(PacketType::PACKET_ATTACK))
2276
                return;
2277
            logger->log("LocalPlayer::targetMoved");
2278
            playerHandler->attack(mTarget->getId(), mServerAttack);
2279
        }
2280
    }
2281
*/
2282
}
2283
2284
int LocalPlayer::getPathLength(const Being *const being) const
2285
{
2286
    if ((mMap == nullptr) || (being == nullptr))
2287
        return 0;
2288
2289
    if (being->mX == mX && being->mY == mY)
2290
        return 0;
2291
2292
    if (being->mX - 1 <= mX &&
2293
        being->mX + 1 >= mX &&
2294
        being->mY - 1 <= mY &&
2295
        being->mY + 1 >= mY)
2296
    {
2297
        return 1;
2298
    }
2299
2300
    if (mTargetOnlyReachable)
2301
    {
2302
        const Path debugPath = mMap->findPath(
2303
            (mPixelX - mapTileSize / 2) / mapTileSize,
2304
            (mPixelY - mapTileSize) / mapTileSize,
2305
            being->mX,
2306
            being->mY,
2307
            getBlockWalkMask(),
2308
            0);
2309
        return CAST_S32(debugPath.size());
2310
    }
2311
2312
    const int dx = CAST_S32(abs(being->mX - mX));
2313
    const int dy = CAST_S32(abs(being->mY - mY));
2314
    if (dx > dy)
2315
        return dx;
2316
    return dy;
2317
}
2318
2319
int LocalPlayer::getAttackRange2() const
2320
{
2321
    int range = getAttackRange();
2322
    if (range == 1)
2323
        range = 2;
2324
    return range;
2325
}
2326
2327
void LocalPlayer::attack2(Being *const target,
2328
                          const bool keep,
2329
                          const bool dontChangeEquipment)
2330
{
2331
    if (!dontChangeEquipment && (target != nullptr))
2332
        changeEquipmentBeforeAttack(target);
2333
2334
    const bool broken = (Net::getNetworkType() == ServerType::TMWATHENA);
2335
2336
    // probably need cache getPathLength(target)
2337
    if ((target == nullptr ||
2338
        settings.attackType == 0 ||
2339
        settings.attackType == 3) ||
2340
        (withinAttackRange(target, broken, broken ? 1 : 0) &&
2341
        getPathLength(target) <= getAttackRange2()))
2342
    {
2343
        attack(target, keep, false);
2344
        if (settings.attackType == 2)
2345
        {
2346
            if (target == nullptr)
2347
            {
2348
                if (pickUpItems(0))
2349
                    return;
2350
            }
2351
            else
2352
            {
2353
                pickUpItems(3);
2354
            }
2355
        }
2356
    }
2357
    else if (mPickUpTarget == nullptr)
2358
    {
2359
        if (settings.attackType == 2)
2360
        {
2361
            if (pickUpItems(0))
2362
                return;
2363
        }
2364
        setTarget(target);
2365
        if (target->getType() != ActorType::Npc)
2366
        {
2367
            mKeepAttacking = true;
2368
            moveToTarget(-1);
2369
        }
2370
    }
2371
}
2372
2373
void LocalPlayer::setFollow(const std::string &player)
2374
{
2375
    mPlayerFollowed = player;
2376
    if (!mPlayerFollowed.empty())
2377
    {
2378
        // TRANSLATORS: follow command message
2379
        std::string msg = strprintf(_("Follow: %s"), player.c_str());
2380
        debugMsg(msg)
2381
    }
2382
    else
2383
    {
2384
        // TRANSLATORS: follow command message
2385
        debugMsg(_("Follow canceled"))
2386
    }
2387
}
2388
2389
void LocalPlayer::setImitate(const std::string &player)
2390
{
2391
    mPlayerImitated = player;
2392
    if (!mPlayerImitated.empty())
2393
    {
2394
        // TRANSLATORS: imitate command message
2395
        std::string msg = strprintf(_("Imitation: %s"), player.c_str());
2396
        debugMsg(msg)
2397
    }
2398
    else
2399
    {
2400
        // TRANSLATORS: imitate command message
2401
        debugMsg(_("Imitation canceled"))
2402
    }
2403
}
2404
2405
void LocalPlayer::cancelFollow()
2406
{
2407
    if (!mPlayerFollowed.empty())
2408
    {
2409
        // TRANSLATORS: cancel follow message
2410
        debugMsg(_("Follow canceled"))
2411
    }
2412
    if (!mPlayerImitated.empty())
2413
    {
2414
        // TRANSLATORS: cancel follow message
2415
        debugMsg(_("Imitation canceled"))
2416
    }
2417
    mPlayerFollowed.clear();
2418
    mPlayerImitated.clear();
2419
}
2420
2421
void LocalPlayer::imitateEmote(const Being *const being,
2422
                               const unsigned char action) const
2423
{
2424
    if (being == nullptr)
2425
        return;
2426
2427
    std::string player_imitated = getImitate();
2428
    if (!player_imitated.empty() && being->mName == player_imitated)
2429
        emote(action);
2430
}
2431
2432
void LocalPlayer::imitateAction(const Being *const being,
2433
                                const BeingActionT &action)
2434
{
2435
    if (being == nullptr)
2436
        return;
2437
2438
    if (!mPlayerImitated.empty() && being->mName == mPlayerImitated)
2439
    {
2440
        setAction(action, 0);
2441
        playerHandler->changeAction(action);
2442
    }
2443
}
2444
2445
void LocalPlayer::imitateDirection(const Being *const being,
2446
                                   const unsigned char dir)
2447
{
2448
    if (being == nullptr)
2449
        return;
2450
2451
    if (!mPlayerImitated.empty() && being->mName == mPlayerImitated)
2452
    {
2453
        if (!PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION))
2454
            return;
2455
2456
        if (settings.followMode == 2)
2457
        {
2458
            uint8_t dir2 = 0;
2459
            if ((dir & BeingDirection::LEFT) != 0)
2460
                dir2 |= BeingDirection::RIGHT;
2461
            else if ((dir & BeingDirection::RIGHT) != 0)
2462
                dir2 |= BeingDirection::LEFT;
2463
            if ((dir & BeingDirection::UP) != 0)
2464
                dir2 |= BeingDirection::DOWN;
2465
            else if ((dir & BeingDirection::DOWN) != 0)
2466
                dir2 |= BeingDirection::UP;
2467
2468
            setDirection(dir2);
2469
            playerHandler->setDirection(dir2);
2470
        }
2471
        else
2472
        {
2473
            setDirection(dir);
2474
            playerHandler->setDirection(dir);
2475
        }
2476
    }
2477
}
2478
2479
void LocalPlayer::imitateOutfit(const Being *const player,
2480
                                const int sprite) const
2481
{
2482
    if (player == nullptr)
2483
        return;
2484
2485
    if (settings.imitationMode == 1 &&
2486
        !mPlayerImitated.empty() &&
2487
        player->mName == mPlayerImitated)
2488
    {
2489
        if (sprite < 0 || sprite >= player->getNumberOfLayers())
2490
            return;
2491
2492
        const AnimatedSprite *const equipmentSprite
2493
            = dynamic_cast<const AnimatedSprite *>(
2494
            player->mSprites[sprite]);
2495
2496
        if (equipmentSprite != nullptr)
2497
        {
2498
//            logger->log("have equipmentSprite");
2499
            const Inventory *const inv = PlayerInfo::getInventory();
2500
            if (inv == nullptr)
2501
                return;
2502
2503
            const std::string &path = equipmentSprite->getIdPath();
2504
            if (path.empty())
2505
                return;
2506
2507
//            logger->log("idPath: " + path);
2508
            const Item *const item = inv->findItemBySprite(path,
2509
                player->getGender(), player->getSubType());
2510
            if ((item != nullptr) && item->isEquipped() == Equipped_false)
2511
                PlayerInfo::equipItem(item, Sfx_false);
2512
        }
2513
        else
2514
        {
2515
//            logger->log("have unequip %d", sprite);
2516
            const int equipmentSlot = inventoryHandler
2517
                ->convertFromServerSlot(sprite);
2518
//            logger->log("equipmentSlot: " + toString(equipmentSlot));
2519
            if (equipmentSlot == inventoryHandler->getProjectileSlot())
2520
                return;
2521
2522
            const Item *const item = PlayerInfo::getEquipment(equipmentSlot);
2523
            if (item != nullptr)
2524
            {
2525
//                logger->log("unequiping");
2526
                PlayerInfo::unequipItem(item, Sfx_false);
2527
            }
2528
        }
2529
    }
2530
}
2531
2532
void LocalPlayer::followMoveTo(const Being *const being,
2533
                               const int x, const int y)
2534
{
2535
    if ((being != nullptr) &&
2536
        !mPlayerFollowed.empty() &&
2537
        being->mName == mPlayerFollowed)
2538
    {
2539
        mPickUpTarget = nullptr;
2540
        navigateTo(x, y);
2541
    }
2542
}
2543
2544
void LocalPlayer::followMoveTo(const Being *const being,
2545
                               const int x1, const int y1,
2546
                               const int x2, const int y2)
2547
{
2548
    if (being == nullptr)
2549
        return;
2550
2551
    mPickUpTarget = nullptr;
2552
    if (!mPlayerFollowed.empty() &&
2553
        being->mName == mPlayerFollowed)
2554
    {
2555
        switch (settings.followMode)
2556
        {
2557
            case 0:
2558
                navigateTo(x1, y1);
2559
                setNextDest(x2, y2);
2560
                break;
2561
            case 1:
2562
                if (x1 != x2 || y1 != y2)
2563
                {
2564
                    navigateTo(mX + x2 - x1, mY + y2 - y1);
2565
                    setNextDest(mX + x2 - x1, mY + y2 - y1);
2566
                }
2567
                break;
2568
            case 2:
2569
                if (x1 != x2 || y1 != y2)
2570
                {
2571
                    navigateTo(mX + x1 - x2, mY + y1 - y2);
2572
                    setNextDest(mX + x1 - x2, mY + y1 - y2);
2573
                }
2574
                break;
2575
            case 3:
2576
                if (mTarget == nullptr ||
2577
                    mTarget->mName != mPlayerFollowed)
2578
                {
2579
                    if (actorManager != nullptr)
2580
                    {
2581
                        Being *const b = actorManager->findBeingByName(
2582
                            mPlayerFollowed, ActorType::Player);
2583
                        setTarget(b);
2584
                    }
2585
                }
2586
                moveToTarget(-1);
2587
                setNextDest(x2, y2);
2588
                break;
2589
            default:
2590
                break;
2591
        }
2592
    }
2593
}
2594
2595
void LocalPlayer::setNextDest(const int x, const int y)
2596
{
2597
    mNextDestX = x;
2598
    mNextDestY = y;
2599
}
2600
2601
bool LocalPlayer::allowAction()
2602
{
2603
    if (mIsServerBuggy)
2604
    {
2605
        if (mLastAction != -1)
2606
            return false;
2607
        mLastAction = tick_time;
2608
    }
2609
    return true;
2610
}
2611
2612
void LocalPlayer::fixPos()
2613
{
2614
    if ((mCrossX == 0) && (mCrossY == 0))
2615
        return;
2616
2617
    const int dx = (mX >= mCrossX) ? mX - mCrossX : mCrossX - mX;
2618
    const int dy = (mY >= mCrossY) ? mY - mCrossY : mCrossY - mY;
2619
    const int dist = dx > dy ? dx : dy;
2620
    const time_t time = cur_time;
2621
2622
#ifdef TMWA_SUPPORT
2623
    int maxDist;
2624
    if (mSyncPlayerMove)
2625
    {
2626
        maxDist = mSyncPlayerMoveDistance;
2627
    }
2628
    else
2629
    {
2630
        if (Net::getNetworkType() == ServerType::TMWATHENA)
2631
            maxDist = 30;
2632
        else
2633
            maxDist = 10;
2634
    }
2635
#else
2636
    const int maxDist = mSyncPlayerMove ? mSyncPlayerMoveDistance : 10;
2637
#endif
2638
2639
    if (dist > maxDist)
2640
    {
2641
        mActivityTime = time;
2642
#ifdef ENABLEDEBUGLOG
2643
        logger->dlog(strprintf("Fix position from (%d,%d) to (%d,%d)",
2644
            mX, mY,
2645
            mCrossX, mCrossY));
2646
#endif
2647
        setTileCoords(mCrossX, mCrossY);
2648
/*
2649
        if (mNavigateX != 0 || mNavigateY != 0)
2650
        {
2651
#ifdef ENABLEDEBUGLOG
2652
            logger->dlog(strprintf("Renavigate to (%d,%d)",
2653
                mNavigateX, mNavigateY));
2654
#endif
2655
            navigateTo(mNavigateX, mNavigateY);
2656
        }
2657
*/
2658
// alternative way to fix, move to real position
2659
//        setDestination(mCrossX, mCrossY);
2660
    }
2661
}
2662
2663
void LocalPlayer::setTileCoords(const int x, const int y) restrict2
2664
{
2665
    Being::setTileCoords(x, y);
2666
    mCrossX = x;
2667
    mCrossY = y;
2668
}
2669
2670
void LocalPlayer::setRealPos(const int x, const int y)
2671
{
2672
    if (mMap == nullptr)
2673
        return;
2674
2675
    SpecialLayer *const layer = mMap->getTempLayer();
2676
    if (layer != nullptr)
2677
    {
2678
        bool cacheUpdated(false);
2679
        if (((mCrossX != 0) || (mCrossY != 0)) &&
2680
            (layer->getTile(mCrossX, mCrossY) != nullptr) &&
2681
            layer->getTile(mCrossX, mCrossY)->getType() == MapItemType::CROSS)
2682
        {
2683
            layer->setTile(mCrossX, mCrossY, MapItemType::EMPTY);
2684
            layer->updateCache();
2685
            cacheUpdated = true;
2686
        }
2687
2688
        if (mShowServerPos)
2689
        {
2690
            const MapItem *const mapItem = layer->getTile(x, y);
2691
2692
            if (mapItem == nullptr ||
2693
                mapItem->getType() == MapItemType::EMPTY)
2694
            {
2695
                if (mX != x && mY != y)
2696
                {
2697
                    layer->setTile(x, y, MapItemType::CROSS);
2698
                    if (cacheUpdated == false)
2699
                        layer->updateCache();
2700
                }
2701
            }
2702
        }
2703
2704
        if (mCrossX != x || mCrossY != y)
2705
        {
2706
            mCrossX = x;
2707
            mCrossY = y;
2708
            // +++ possible configuration option
2709
            fixPos();
2710
        }
2711
    }
2712
    if (mMap->isCustom())
2713
        mMap->setWalk(x, y);
2714
}
2715
2716
void LocalPlayer::fixAttackTarget()
2717
{
2718
    if ((mMap == nullptr) || (mTarget == nullptr))
2719
        return;
2720
2721
    if (settings.moveToTargetType == 11 || (settings.attackType == 0U)
2722
        || !config.getBoolValue("autofixPos"))
2723
    {
2724
        return;
2725
    }
2726
2727
    const Path debugPath = mMap->findPath(
2728
        (mPixelX - mapTileSize / 2) / mapTileSize,
2729
        (mPixelY - mapTileSize) / mapTileSize,
2730
        mTarget->mX,
2731
        mTarget->mY,
2732
        getBlockWalkMask(),
2733
        0);
2734
2735
    if (!debugPath.empty())
2736
    {
2737
        const Path::const_iterator i = debugPath.begin();
2738
        setDestination((*i).x, (*i).y);
2739
    }
2740
}
2741
2742
void LocalPlayer::respawn()
2743
{
2744
    navigateClean();
2745
}
2746
2747
1
int LocalPlayer::getLevel() const
2748
{
2749
1
    return PlayerInfo::getAttribute(Attributes::PLAYER_BASE_LEVEL);
2750
}
2751
2752
188
void LocalPlayer::updateNavigateList()
2753
{
2754
✗✓
188
    if (mMap != nullptr)
2755
    {
2756
        const std::map<std::string, Vector>::const_iterator iter =
2757
            mHomes.find(mMap->getProperty("_realfilename", std::string()));
2758
2759
        if (iter != mHomes.end())
2760
        {
2761
            const Vector &pos = mHomes[(*iter).first];
2762
            if ((pos.x != 0.0F) && (pos.y != 0.0F))
2763
            {
2764
                mMap->addPortalTile("home", MapItemType::HOME,
2765
                    CAST_S32(pos.x), CAST_S32(pos.y));
2766
            }
2767
        }
2768
    }
2769
188
}
2770
2771
void LocalPlayer::failMove(const int x A_UNUSED,
2772
                           const int y A_UNUSED)
2773
{
2774
    fixPos();
2775
}
2776
2777
void LocalPlayer::waitFor(const std::string &nick)
2778
{
2779
    mWaitFor = nick;
2780
}
2781
2782
void LocalPlayer::checkNewName(Being *const being)
2783
{
2784
    if (being == nullptr)
2785
        return;
2786
2787
    const std::string &nick = being->mName;
2788
    if (being->getType() == ActorType::Player)
2789
    {
2790
        const Guild *const guild = getGuild();
2791
        if (guild != nullptr)
2792
        {
2793
            const GuildMember *const gm = guild->getMember(nick);
2794
            if (gm != nullptr)
2795
            {
2796
                const int level = gm->getLevel();
2797
                if (level > 1 && being->getLevel() != level)
2798
                {
2799
                    being->setLevel(level);
2800
                    being->updateName();
2801
                }
2802
            }
2803
        }
2804
        if (chatWindow != nullptr)
2805
        {
2806
            WhisperTab *const tab = chatWindow->getWhisperTab(nick);
2807
            if (tab != nullptr)
2808
                tab->setWhisperTabColors();
2809
        }
2810
    }
2811
2812
    if (!mWaitFor.empty() && mWaitFor == nick)
2813
    {
2814
        // TRANSLATORS: wait player/monster message
2815
        debugMsg(strprintf(_("You see %s"), mWaitFor.c_str()))
2816
        soundManager.playGuiSound(SOUND_INFO);
2817
        mWaitFor.clear();
2818
    }
2819
}
2820
2821
unsigned char LocalPlayer::getBlockWalkMask() const
2822
{
2823
    // for now blocking all types of collisions
2824
    return BlockMask::WALL |
2825
        BlockMask::AIR |
2826
        BlockMask::WATER |
2827
        BlockMask::PLAYERWALL;
2828
}
2829
2830
void LocalPlayer::removeHome()
2831
{
2832
    if (mMap == nullptr)
2833
        return;
2834
2835
    const std::string key = mMap->getProperty("_realfilename", std::string());
2836
    const std::map<std::string, Vector>::iterator iter = mHomes.find(key);
2837
2838
    if (iter != mHomes.end())
2839
        mHomes.erase(key);
2840
}
2841
2842
void LocalPlayer::stopAdvert()
2843
{
2844
    mBlockAdvert = true;
2845
}
2846
2847
bool LocalPlayer::checAttackPermissions(const Being *const target)
2848
{
2849
    if (target == nullptr)
2850
        return false;
2851
2852
    switch (settings.pvpAttackType)
2853
    {
2854
        case 0:
2855
            return true;
2856
        case 1:
2857
            return !(playerRelations.getRelation(target->mName)
2858
                 == Relation::FRIEND);
2859
        case 2:
2860
            return playerRelations.checkBadRelation(target->mName);
2861
        default:
2862
        case 3:
2863
            return false;
2864
    }
2865
}
2866
2867
void LocalPlayer::updateStatus() const
2868
{
2869
    if (serverFeatures->havePlayerStatusUpdate() && mEnableAdvert)
2870
    {
2871
        uint8_t status = 0;
2872
        if (Net::getNetworkType() == ServerType::TMWATHENA)
2873
        {
2874
            if (mTradebot &&
2875
                shopWindow != nullptr &&
2876
                !shopWindow->isShopEmpty())
2877
            {
2878
                status |= BeingFlag::SHOP;
2879
            }
2880
        }
2881
        if (settings.awayMode || settings.pseudoAwayMode)
2882
            status |= BeingFlag::AWAY;
2883
2884
        if (mInactive)
2885
            status |= BeingFlag::INACTIVE;
2886
2887
        playerHandler->updateStatus(status);
2888
    }
2889
}
2890
2891
void LocalPlayer::setTestParticle(const std::string &fileName,
2892
                                  const bool updateHash)
2893
{
2894
    mTestParticleName = fileName;
2895
    mTestParticleTime = cur_time;
2896
    if (mTestParticle != nullptr)
2897
    {
2898
        mChildParticleEffects.removeLocally(mTestParticle);
2899
        mTestParticle = nullptr;
2900
    }
2901
    if (!fileName.empty())
2902
    {
2903
        mTestParticle = particleEngine->addEffect(fileName, 0, 0, 0);
2904
        controlCustomParticle(mTestParticle);
2905
        if (updateHash)
2906
            mTestParticleHash = UpdaterWindow::getFileHash(mTestParticleName);
2907
    }
2908
}
2909
2910
void LocalPlayer::playerDeath()
2911
{
2912
    if (mAction != BeingAction::DEAD)
2913
    {
2914
        setAction(BeingAction::DEAD, 0);
2915
        recalcSpritesOrder();
2916
    }
2917
}
2918
2919
bool LocalPlayer::canMove() const
2920
{
2921
    return !mFreezed &&
2922
        mAction != BeingAction::DEAD &&
2923
        (serverFeatures->haveMoveWhileSit() ||
2924
        mAction != BeingAction::SIT);
2925
}
2926
2927
void LocalPlayer::freezeMoving(const int timeWaitTicks)
2928
{
2929
    if (timeWaitTicks <= 0)
2930
        return;
2931
    const int nextTime = tick_time + timeWaitTicks;
2932
    if (mUnfreezeTime < nextTime)
2933
        mUnfreezeTime = nextTime;
2934
    if (mUnfreezeTime > 0)
2935
        mFreezed = true;
2936
✓✗✓✗
3
}