GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/localplayer.cpp Lines: 106 1301 8.1 %
Date: 2019-03-24 Branches: 111 1705 6.5 %

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
 *
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 "being/localplayer.h"
24
25
#include "actormanager.h"
26
#include "configuration.h"
27
#include "gamemodifiers.h"
28
#include "guild.h"
29
#include "party.h"
30
#include "settings.h"
31
#include "soundmanager.h"
32
#include "statuseffect.h"
33
34
#include "being/beingflag.h"
35
#include "being/crazymoves.h"
36
#include "being/playerinfo.h"
37
#include "being/playerrelations.h"
38
39
#include "const/sound.h"
40
41
#include "enums/equipslot.h"
42
43
#include "enums/being/beingdirection.h"
44
45
#include "enums/resources/map/blockmask.h"
46
#include "enums/resources/map/mapitemtype.h"
47
48
#include "particle/particleengine.h"
49
50
#include "input/keyboardconfig.h"
51
52
#include "gui/gui.h"
53
#include "gui/userpalette.h"
54
#include "gui/popupmanager.h"
55
56
#include "gui/windows/chatwindow.h"
57
#include "gui/windows/ministatuswindow.h"
58
#include "gui/windows/okdialog.h"
59
#include "gui/windows/outfitwindow.h"
60
#include "gui/windows/shopwindow.h"
61
#include "gui/windows/socialwindow.h"
62
#include "gui/windows/statuswindow.h"
63
#include "gui/windows/updaterwindow.h"
64
65
#include "gui/widgets/tabs/chat/whispertab.h"
66
67
#include "listeners/awaylistener.h"
68
69
#include "net/beinghandler.h"
70
#include "net/chathandler.h"
71
#include "net/inventoryhandler.h"
72
#include "net/net.h"
73
#include "net/packetlimiter.h"
74
#include "net/playerhandler.h"
75
#include "net/serverfeatures.h"
76
77
#include "resources/iteminfo.h"
78
79
#include "resources/db/weaponsdb.h"
80
81
#include "resources/item/item.h"
82
83
#include "resources/map/map.h"
84
#include "resources/map/mapitem.h"
85
#include "resources/map/speciallayer.h"
86
#include "resources/map/walklayer.h"
87
88
#include "resources/sprite/animatedsprite.h"
89
90
#include "utils/delete2.h"
91
#include "utils/foreach.h"
92
#include "utils/gettext.h"
93
#include "utils/timer.h"
94
95
#ifdef USE_MUMBLE
96
#include "mumblemanager.h"
97
#endif  // USE_MUMBLE
98
99
#include <climits>
100
101
#include "debug.h"
102
103
static const int16_t awayLimitTimer = 60;
104
static const int MAX_TICK_VALUE = INT_MAX / 2;
105
106
typedef std::map<int, Guild*>::const_iterator GuildMapCIter;
107
108
LocalPlayer *localPlayer = nullptr;
109
110
extern OkDialog *weightNotice;
111
extern time_t weightNoticeTime;
112
113
94
LocalPlayer::LocalPlayer(const BeingId id,
114
94
                         const BeingTypeId subType) :
115
    Being(id, ActorType::Player),
116
    ActorSpriteListener(),
117
    AttributeListener(),
118
    PlayerDeathListener(),
119
    mMoveState(0),
120
    mLastTargetX(0),
121
    mLastTargetY(0),
122
    mHomes(),
123
    mTarget(nullptr),
124
    mPlayerFollowed(),
125
    mPlayerImitated(),
126
    mNextDestX(0),
127
    mNextDestY(0),
128
    mPickUpTarget(nullptr),
129
    mLastAction(-1),
130
    mStatusEffectIcons(),
131
    mMessages(),
132
    mMessageTime(0),
133
188
    mAwayListener(new AwayListener),
134
    mAwayDialog(nullptr),
135
    mPingSendTick(0),
136
    mPingTime(0),
137
    mAfkTime(0),
138
    mActivityTime(0),
139
    mNavigateX(0),
140
    mNavigateY(0),
141
    mNavigateId(BeingId_zero),
142
    mCrossX(0),
143
    mCrossY(0),
144
    mOldX(0),
145
    mOldY(0),
146
    mOldTileX(0),
147
    mOldTileY(0),
148
    mNavigatePath(),
149
    mLastHitFrom(),
150
    mWaitFor(),
151
    mAdvertTime(0),
152
    mTestParticle(nullptr),
153
    mTestParticleName(),
154
    mTestParticleTime(0),
155
    mTestParticleHash(0L),
156

376
    mSyncPlayerMoveDistance(config.getIntValue("syncPlayerMoveDistance")),
157
    mUnfreezeTime(0),
158
    mWalkingDir(0),
159
    mUpdateName(true),
160
    mBlockAdvert(false),
161

376
    mTargetDeadPlayers(config.getBoolValue("targetDeadPlayers")),
162

376
    mServerAttack(fromBool(config.getBoolValue("serverAttack"), Keep)),
163
94
    mVisibleNames(static_cast<VisibleName::Type>(
164

376
        config.getIntValue("visiblenames"))),
165

376
    mEnableAdvert(config.getBoolValue("enableAdvert")),
166

376
    mTradebot(config.getBoolValue("tradebot")),
167

376
    mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")),
168

376
    mIsServerBuggy(serverConfig.getValueBool("enableBuggyServers", true)),
169

376
    mSyncPlayerMove(config.getBoolValue("syncPlayerMove")),
170

376
    mDrawPath(config.getBoolValue("drawPath")),
171

376
    mAttackMoving(config.getBoolValue("attackMoving")),
172

376
    mAttackNext(config.getBoolValue("attackNext")),
173

376
    mShowJobExp(config.getBoolValue("showJobExp")),
174

376
    mShowServerPos(config.getBoolValue("showserverpos")),
175
    mNextStep(false),
176
    mGoingToTarget(false),
177
    mKeepAttacking(false),
178
    mPathSetByMouse(false),
179
    mWaitPing(false),
180
    mShowNavigePath(false),
181
    mAllowRename(false),
182

2632
    mFreezed(false)
183
{
184
94
    logger->log1("LocalPlayer::LocalPlayer");
185
186
94
    postInit(subType, nullptr);
187
94
    mAttackRange = 0;
188
94
    mLevel = 1;
189
94
    mAdvanced = true;
190
94
    mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
191
94
    if (userPalette != nullptr)
192
69
        mNameColor = &userPalette->getColor(UserColorId::SELF, 255U);
193
    else
194
25
        mNameColor = nullptr;
195
196
188
    PlayerInfo::setStatBase(Attributes::PLAYER_WALK_SPEED,
197
        getWalkSpeed(),
198
94
        Notify_true);
199
    PlayerInfo::setStatMod(Attributes::PLAYER_WALK_SPEED,
200
        0,
201
94
        Notify_true);
202
203
94
    loadHomes();
204
205

376
    config.addListener("showownname", this);
206

376
    config.addListener("targetDeadPlayers", this);
207

376
    serverConfig.addListener("enableBuggyServers", this);
208

376
    config.addListener("syncPlayerMove", this);
209

376
    config.addListener("syncPlayerMoveDistance", this);
210

376
    config.addListener("drawPath", this);
211

376
    config.addListener("serverAttack", this);
212

376
    config.addListener("attackMoving", this);
213

376
    config.addListener("attackNext", this);
214

376
    config.addListener("showJobExp", this);
215

376
    config.addListener("enableAdvert", this);
216

376
    config.addListener("tradebot", this);
217

376
    config.addListener("targetOnlyReachable", this);
218

376
    config.addListener("showserverpos", this);
219

376
    config.addListener("visiblenames", this);
220

376
    setShowName(config.getBoolValue("showownname"));
221
94
}
222
223
1222
LocalPlayer::~LocalPlayer()
224
{
225
94
    logger->log1("LocalPlayer::~LocalPlayer");
226
227
94
    config.removeListeners(this);
228
376
    serverConfig.removeListener("enableBuggyServers", this);
229
230
94
    navigateClean();
231
94
    mCrossX = 0;
232
94
    mCrossY = 0;
233
234
94
    updateNavigateList();
235
236
94
    if (mAwayDialog != nullptr)
237
    {
238
        soundManager.volumeRestore();
239
        delete2(mAwayDialog)
240
    }
241
188
    delete2(mAwayListener)
242
188
}
243
244
void LocalPlayer::logic()
245
{
246
    BLOCK_START("LocalPlayer::logic")
247
#ifdef USE_MUMBLE
248
    if (mumbleManager)
249
        mumbleManager->setPos(mX, mY, mDirection);
250
#endif  // USE_MUMBLE
251
252
    // Actions are allowed once per second
253
    if (get_elapsed_time(mLastAction) >= 1000)
254
        mLastAction = -1;
255
256
    if (mActivityTime == 0 || mLastAction != -1)
257
        mActivityTime = cur_time;
258
259
    if (mUnfreezeTime > 0 &&
260
        mUnfreezeTime <= tick_time)
261
    {
262
        mUnfreezeTime = 0;
263
        mFreezed = false;
264
    }
265
266
    if ((mAction != BeingAction::MOVE || mNextStep) && !mNavigatePath.empty())
267
    {
268
        mNextStep = false;
269
        int dist = 5;
270
        if (!mSyncPlayerMove)
271
            dist = 20;
272
273
        if (((mNavigateX != 0) || (mNavigateY != 0)) &&
274
            ((mCrossX + dist >= mX && mCrossX <= mX + dist
275
            && mCrossY + dist >= mY && mCrossY <= mY + dist)
276
            || ((mCrossX == 0) && (mCrossY == 0))))
277
        {
278
            const Path::const_iterator i = mNavigatePath.begin();
279
            if ((*i).x == mX && (*i).y == mY)
280
                mNavigatePath.pop_front();
281
            else
282
                setDestination((*i).x, (*i).y);
283
        }
284
    }
285
286
    // Show XP messages
287
    if (!mMessages.empty())
288
    {
289
        if (mMessageTime == 0)
290
        {
291
            const MessagePair info = mMessages.front();
292
293
            if ((particleEngine != nullptr) && (gui != nullptr))
294
            {
295
                particleEngine->addTextRiseFadeOutEffect(
296
                    info.first,
297
                    mPixelX,
298
                    mPixelY - 48,
299
                    &userPalette->getColor(info.second, 255U),
300
                    gui->getInfoParticleFont(),
301
                    true);
302
            }
303
304
            mMessages.pop_front();
305
            mMessageTime = 30;
306
        }
307
        mMessageTime--;
308
    }
309
310
    if (mTarget != nullptr)
311
    {
312
        if (mTarget->getType() == ActorType::Npc)
313
        {
314
            // NPCs are always in range
315
            mTarget->setTargetType(TargetCursorType::IN_RANGE);
316
        }
317
        else
318
        {
319
            // Find whether target is in range
320
            const int rangeX = CAST_S32(
321
                abs(mTarget->mX - mX));
322
            const int rangeY = CAST_S32(
323
                abs(mTarget->mY - mY));
324
            const int attackRange = getAttackRange();
325
            const TargetCursorTypeT targetType
326
                = rangeX > attackRange || rangeY > attackRange
327
                ? TargetCursorType::NORMAL : TargetCursorType::IN_RANGE;
328
            mTarget->setTargetType(targetType);
329
330
            if (!mTarget->isAlive() && (!mTargetDeadPlayers
331
                || mTarget->getType() != ActorType::Player))
332
            {
333
                stopAttack(true);
334
            }
335
336
            if (mKeepAttacking && (mTarget != nullptr))
337
                attack(mTarget, true, false);
338
        }
339
    }
340
341
    Being::logic();
342
    BLOCK_END("LocalPlayer::logic")
343
}
344
345
void LocalPlayer::slowLogic()
346
{
347
    BLOCK_START("LocalPlayer::slowLogic")
348
    const time_t time = cur_time;
349
    if ((weightNotice != nullptr) && weightNoticeTime < time)
350
    {
351
        weightNotice->scheduleDelete();
352
        weightNotice = nullptr;
353
        weightNoticeTime = 0;
354
    }
355
356
    if ((serverFeatures != nullptr) &&
357
        !serverFeatures->havePlayerStatusUpdate() &&
358
        mEnableAdvert &&
359
        !mBlockAdvert &&
360
        mAdvertTime < cur_time)
361
    {
362
        uint8_t smile = BeingFlag::SPECIAL;
363
        if (mTradebot &&
364
            shopWindow != nullptr &&
365
            !shopWindow->isShopEmpty())
366
        {
367
            smile |= BeingFlag::SHOP;
368
        }
369
370
        if (settings.awayMode || settings.pseudoAwayMode)
371
            smile |= BeingFlag::AWAY;
372
373
        if (mInactive)
374
            smile |= BeingFlag::INACTIVE;
375
376
        if (emote(smile))
377
            mAdvertTime = time + 60;
378
        else
379
            mAdvertTime = time + 30;
380
    }
381
382
    if (mTestParticleTime != time && !mTestParticleName.empty())
383
    {
384
        const unsigned long hash = UpdaterWindow::getFileHash(
385
            mTestParticleName);
386
        if (hash != mTestParticleHash)
387
        {
388
            setTestParticle(mTestParticleName, false);
389
            mTestParticleHash = hash;
390
        }
391
        mTestParticleTime = time;
392
    }
393
394
    BLOCK_END("LocalPlayer::slowLogic")
395
}
396
397
void LocalPlayer::setAction(const BeingActionT &action,
398
                            const int attackId)
399
{
400
    if (action == BeingAction::DEAD)
401
    {
402
        if (!mLastHitFrom.empty() &&
403
            !serverFeatures->haveKillerId())
404
        {
405
            // TRANSLATORS: chat message after death
406
            debugMsg(strprintf(_("You were killed by %s."),
407
                mLastHitFrom.c_str()))
408
            mLastHitFrom.clear();
409
        }
410
        setTarget(nullptr);
411
    }
412
413
    Being::setAction(action,
414
        attackId);
415
#ifdef USE_MUMBLE
416
    if (mumbleManager)
417
        mumbleManager->setAction(CAST_S32(action));
418
#endif  // USE_MUMBLE
419
}
420
421
void LocalPlayer::setGroupId(const int id)
422
{
423
    Being::setGroupId(id);
424
425
    if (mIsGM != 0)
426
    {
427
        if (chatWindow != nullptr)
428
        {
429
            chatWindow->loadGMCommands();
430
            chatWindow->showGMTab();
431
        }
432
    }
433
    if (statusWindow != nullptr)
434
        statusWindow->updateLevelLabel();
435
}
436
437
void LocalPlayer::nextTile()
438
{
439
    const Party *const party = Party::getParty(1);
440
    if (party != nullptr)
441
    {
442
        PartyMember *const pm = party->getMember(mName);
443
        if (pm != nullptr)
444
        {
445
            pm->setX(mX);
446
            pm->setY(mY);
447
        }
448
    }
449
450
    if (mPath.empty())
451
    {
452
        if (mPickUpTarget != nullptr)
453
            pickUp(mPickUpTarget);
454
455
        if (mWalkingDir != 0U)
456
            startWalking(mWalkingDir);
457
    }
458
    else if (mPath.size() == 1)
459
    {
460
        if (mPickUpTarget != nullptr)
461
            pickUp(mPickUpTarget);
462
    }
463
464
    if (mGoingToTarget &&
465
        mTarget != nullptr &&
466
        withinAttackRange(mTarget, false, 0))
467
    {
468
        mAction = BeingAction::STAND;
469
        attack(mTarget, true, false);
470
        mGoingToTarget = false;
471
        mPath.clear();
472
        return;
473
    }
474
    else if (mGoingToTarget && (mTarget == nullptr))
475
    {
476
        mGoingToTarget = false;
477
        mPath.clear();
478
    }
479
480
    if (mPath.empty())
481
    {
482
        if (mNavigatePath.empty() || mAction != BeingAction::MOVE)
483
        {
484
            setAction(BeingAction::STAND, 0);
485
            // +++ probably sync position here always?
486
        }
487
        else
488
        {
489
            mNextStep = true;
490
        }
491
    }
492
    else
493
    {
494
        Being::nextTile();
495
    }
496
497
    fixPos();
498
}
499
500
bool LocalPlayer::pickUp(FloorItem *const item)
501
{
502
    if (item == nullptr)
503
        return false;
504
505
    if (!PacketLimiter::limitPackets(PacketType::PACKET_PICKUP))
506
        return false;
507
508
    const int dx = item->getTileX() - mX;
509
    const int dy = item->getTileY() - mY;
510
    int dist = 6;
511
512
    const unsigned int pickUpType = settings.pickUpType;
513
    if (pickUpType >= 4 && pickUpType <= 6)
514
        dist = 4;
515
516
    if (dx * dx + dy * dy < dist)
517
    {
518
        if ((actorManager != nullptr) && actorManager->checkForPickup(item))
519
        {
520
            PlayerInfo::pickUpItem(item, Sfx_true);
521
            mPickUpTarget = nullptr;
522
        }
523
    }
524
    else if (pickUpType >= 4 && pickUpType <= 6)
525
    {
526
        const Path debugPath = mMap->findPath(
527
            (mPixelX - mapTileSize / 2) / mapTileSize,
528
            (mPixelY - mapTileSize) / mapTileSize,
529
            item->getTileX(),
530
            item->getTileY(),
531
            getBlockWalkMask(),
532
            0);
533
        if (!debugPath.empty())
534
            navigateTo(item->getTileX(), item->getTileY());
535
        else
536
            setDestination(item->getTileX(), item->getTileY());
537
538
        mPickUpTarget = item;
539
        mPickUpTarget->addActorSpriteListener(this);
540
    }
541
    return true;
542
}
543
544
void LocalPlayer::actorSpriteDestroyed(const ActorSprite &actorSprite)
545
{
546
    if (mPickUpTarget == &actorSprite)
547
        mPickUpTarget = nullptr;
548
}
549
550
22
Being *LocalPlayer::getTarget() const
551
{
552
22
    return mTarget;
553
}
554
555
7
void LocalPlayer::setTarget(Being *const target)
556
{
557

7
    if (target == this && (target != nullptr))
558
        return;
559
560
7
    if (target == mTarget)
561
        return;
562
563
4
    Being *oldTarget = nullptr;
564
4
    if (mTarget != nullptr)
565
    {
566
2
        mTarget->untarget();
567
2
        oldTarget = mTarget;
568
    }
569
570
4
    if (mTarget != nullptr)
571
    {
572
4
        if (mTarget->getType() == ActorType::Monster)
573
1
            mTarget->setShowName(false);
574
    }
575
576
4
    mTarget = target;
577
578
4
    if (oldTarget != nullptr)
579
2
        oldTarget->updateName();
580
581
4
    if (target != nullptr)
582
    {
583
2
        mLastTargetX = target->mX;
584
2
        mLastTargetY = target->mY;
585
2
        target->updateName();
586
2
        if (mVisibleNames == VisibleName::ShowOnSelection)
587
            target->setShowName(true);
588
    }
589

4
    if (oldTarget != nullptr && mVisibleNames == VisibleName::ShowOnSelection)
590
        oldTarget->setShowName(false);
591

6
    if (target != nullptr && target->getType() == ActorType::Monster)
592
1
        target->setShowName(true);
593
}
594
595
Being *LocalPlayer::setNewTarget(const ActorTypeT type,
596
                                 const AllowSort allowSort)
597
{
598
    if (actorManager != nullptr)
599
    {
600
        Being *const target = actorManager->findNearestLivingBeing(
601
            localPlayer, 20, type, allowSort);
602
603
        if ((target != nullptr) && target != mTarget)
604
            setTarget(target);
605
606
        return target;
607
    }
608
    return nullptr;
609
}
610
611
void LocalPlayer::setDestination(const int x, const int y)
612
{
613
    mActivityTime = cur_time;
614
615
    if (settings.attackType == 0 || !mAttackMoving)
616
        mKeepAttacking = false;
617
618
    // Only send a new message to the server when destination changes
619
    if (x != mDest.x || y != mDest.y)
620
    {
621
        if (settings.moveType != 1)
622
        {
623
            playerHandler->setDestination(x, y, mDirection);
624
            Being::setDestination(x, y);
625
        }
626
        else
627
        {
628
            uint8_t newDir = 0;
629
            if ((mDirection & BeingDirection::UP) != 0)
630
                newDir |= BeingDirection::DOWN;
631
            if ((mDirection & BeingDirection::LEFT) != 0)
632
                newDir |= BeingDirection::RIGHT;
633
            if ((mDirection & BeingDirection::DOWN) != 0)
634
                newDir |= BeingDirection::UP;
635
            if ((mDirection & BeingDirection::RIGHT) != 0)
636
                newDir |= BeingDirection::LEFT;
637
638
            playerHandler->setDestination(x, y, newDir);
639
640
//            if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION))
641
            {
642
                setDirection(newDir);
643
                playerHandler->setDirection(newDir);
644
            }
645
646
            Being::setDestination(x, y);
647
            playerHandler->setDestination(x, y, mDirection);
648
        }
649
    }
650
}
651
652
void LocalPlayer::setWalkingDir(const unsigned char dir)
653
{
654
    // This function is called by Game::handleInput()
655
    mWalkingDir = dir;
656
657
    // If we're not already walking, start walking.
658
    if (mAction != BeingAction::MOVE && (dir != 0U))
659
        startWalking(dir);
660
}
661
662
void LocalPlayer::startWalking(const unsigned char dir)
663
{
664
    // This function is called by setWalkingDir(),
665
    // but also by nextTile() for TMW-Athena...
666
    if ((mMap == nullptr) || (dir == 0U))
667
        return;
668
669
    mPickUpTarget = nullptr;
670
    if (mAction == BeingAction::MOVE && !mPath.empty())
671
    {
672
        // Just finish the current action, otherwise we get out of sync
673
        Being::setDestination(mX, mY);
674
        return;
675
    }
676
677
    int dx = 0;
678
    int dy = 0;
679
    if ((dir & BeingDirection::UP) != 0)
680
        dy--;
681
    if ((dir & BeingDirection::DOWN) != 0)
682
        dy++;
683
    if ((dir & BeingDirection::LEFT) != 0)
684
        dx--;
685
    if ((dir & BeingDirection::RIGHT) != 0)
686
        dx++;
687
688
    const unsigned char blockWalkMask = getBlockWalkMask();
689
    // Prevent skipping corners over colliding tiles
690
    if ((dx != 0) && !mMap->getWalk(mX + dx, mY, blockWalkMask))
691
        dx = 0;
692
    if ((dy != 0) && !mMap->getWalk(mX, mY + dy, blockWalkMask))
693
        dy = 0;
694
695
    // Choose a straight direction when diagonal target is blocked
696
    if (dx != 0 && dy != 0 && !mMap->getWalk(mX + dx, mY + dy, blockWalkMask))
697
        dx = 0;
698
699
    // Walk to where the player can actually go
700
    if ((dx != 0 || dy != 0) && mMap->getWalk(mX + dx, mY + dy, blockWalkMask))
701
    {
702
        setDestination(mX + dx, mY + dy);
703
    }
704
    else if (dir != mDirection)
705
    {
706
        // If the being can't move, just change direction
707
708
//            if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION))
709
        {
710
            playerHandler->setDirection(dir);
711
            setDirection(dir);
712
        }
713
    }
714
}
715
716
void LocalPlayer::stopWalking(const bool sendToServer)
717
{
718
    if (mAction == BeingAction::MOVE && (mWalkingDir != 0U))
719
    {
720
        mWalkingDir = 0;
721
        mPickUpTarget = nullptr;
722
        setDestination(mPixelX,
723
            mPixelY);
724
        if (sendToServer)
725
        {
726
            playerHandler->setDestination(
727
                mPixelX,
728
                mPixelY,
729
                -1);
730
        }
731
        setAction(BeingAction::STAND, 0);
732
    }
733
734
    // No path set anymore, so we reset the path by mouse flag
735
    mPathSetByMouse = false;
736
737
    clearPath();
738
    navigateClean();
739
}
740
741
bool LocalPlayer::toggleSit() const
742
{
743
    if (!PacketLimiter::limitPackets(PacketType::PACKET_SIT))
744
        return false;
745
746
    BeingActionT newAction;
747
    switch (mAction)
748
    {
749
        case BeingAction::STAND:
750
        case BeingAction::PRESTAND:
751
        case BeingAction::SPAWN:
752
            newAction = BeingAction::SIT;
753
            break;
754
        case BeingAction::SIT:
755
            newAction = BeingAction::STAND;
756
            break;
757
        case BeingAction::MOVE:
758
        case BeingAction::ATTACK:
759
        case BeingAction::DEAD:
760
        case BeingAction::HURT:
761
        case BeingAction::CAST:
762
        default:
763
            return true;
764
    }
765
766
    playerHandler->changeAction(newAction);
767
    return true;
768
}
769
770
bool LocalPlayer::updateSit() const
771
{
772
    if (!PacketLimiter::limitPackets(PacketType::PACKET_SIT))
773
        return false;
774
775
    playerHandler->changeAction(mAction);
776
    return true;
777
}
778
779
bool LocalPlayer::emote(const uint8_t emotion)
780
{
781
    if (!PacketLimiter::limitPackets(PacketType::PACKET_EMOTE))
782
        return false;
783
784
    playerHandler->emote(emotion);
785
    return true;
786
}
787
788
void LocalPlayer::attack(Being *const target,
789
                         const bool keep,
790
                         const bool dontChangeEquipment)
791
{
792
    mKeepAttacking = keep;
793
794
    if ((target == nullptr) || target->getType() == ActorType::Npc)
795
        return;
796
797
    if (mTarget != target)
798
        setTarget(target);
799
800
    // Must be standing or sitting or casting to attack
801
    if (mAction != BeingAction::STAND &&
802
        mAction != BeingAction::SIT &&
803
        mAction != BeingAction::CAST)
804
    {
805
        return;
806
    }
807
808
#ifdef TMWA_SUPPORT
809
    const int dist_x = target->mX - mX;
810
    const int dist_y = target->mY - mY;
811
812
    if (Net::getNetworkType() == ServerType::TMWATHENA)
813
    {
814
        if (abs(dist_y) >= abs(dist_x))
815
        {
816
            if (dist_y > 0)
817
                setDirection(BeingDirection::DOWN);
818
            else
819
                setDirection(BeingDirection::UP);
820
        }
821
        else
822
        {
823
            if (dist_x > 0)
824
                setDirection(BeingDirection::RIGHT);
825
            else
826
                setDirection(BeingDirection::LEFT);
827
        }
828
    }
829
#endif  // TMWA_SUPPORT
830
831
    mActionTime = tick_time;
832
833
    if (target->getType() != ActorType::Player
834
        || checAttackPermissions(target))
835
    {
836
        setAction(BeingAction::ATTACK, 0);
837
838
        if (!PacketLimiter::limitPackets(PacketType::PACKET_ATTACK))
839
            return;
840
841
        if (!dontChangeEquipment)
842
            changeEquipmentBeforeAttack(target);
843
844
        const BeingId targetId = target->getId();
845
        playerHandler->attack(targetId, mServerAttack);
846
        PlayerInfo::updateAttackAi(targetId, mServerAttack);
847
    }
848
849
    if (!keep)
850
        stopAttack(false);
851
}
852
853
void LocalPlayer::stopAttack(const bool keepAttack)
854
{
855
    if (!PacketLimiter::limitPackets(PacketType::PACKET_STOPATTACK))
856
        return;
857
858
    if (mServerAttack == Keep_true && mAction == BeingAction::ATTACK)
859
        playerHandler->stopAttack();
860
861
    untarget();
862
    if (!keepAttack || !mAttackNext)
863
        mKeepAttacking = false;
864
}
865
866
void LocalPlayer::untarget()
867
{
868
    if (mAction == BeingAction::ATTACK)
869
        setAction(BeingAction::STAND, 0);
870
871
    if (mTarget != nullptr)
872
        setTarget(nullptr);
873
}
874
875
void LocalPlayer::pickedUp(const ItemInfo &itemInfo,
876
                           const int amount,
877
                           const ItemColor color,
878
                           const BeingId floorItemId,
879
                           const PickupT fail)
880
{
881
    if (fail != Pickup::OKAY)
882
    {
883
        if ((actorManager != nullptr) && floorItemId != BeingId_zero)
884
        {
885
            FloorItem *const item = actorManager->findItem(floorItemId);
886
            if (item != nullptr)
887
            {
888
                if (!item->getShowMsg())
889
                    return;
890
                item->setShowMsg(false);
891
            }
892
        }
893
        const char* msg = nullptr;
894
        switch (fail)
895
        {
896
            case Pickup::BAD_ITEM:
897
                // TRANSLATORS: pickup error message
898
                msg = N_("Tried to pick up nonexistent item.");
899
                break;
900
            case Pickup::TOO_HEAVY:
901
                // TRANSLATORS: pickup error message
902
                msg = N_("Item is too heavy.");
903
                break;
904
            case Pickup::TOO_FAR:
905
                // TRANSLATORS: pickup error message
906
                msg = N_("Item is too far away.");
907
                break;
908
            case Pickup::INV_FULL:
909
                // TRANSLATORS: pickup error message
910
                msg = N_("Inventory is full.");
911
                break;
912
            case Pickup::STACK_FULL:
913
                // TRANSLATORS: pickup error message
914
                msg = N_("Stack is too big.");
915
                break;
916
            case Pickup::DROP_STEAL:
917
                // TRANSLATORS: pickup error message
918
                msg = N_("Item belongs to someone else.");
919
                break;
920
            case Pickup::MAX_AMOUNT:
921
                // TRANSLATORS: pickup error message
922
                msg = N_("You can't pickup this amount of items.");
923
                break;
924
            case Pickup::STACK_AMOUNT:
925
                // TRANSLATORS: pickup error message
926
                msg = N_("Your item stack has max amount.");
927
                break;
928
            case Pickup::OKAY:
929
                break;
930
            default:
931
            case Pickup::UNKNOWN:
932
                // TRANSLATORS: pickup error message
933
                msg = N_("Unknown problem picking up item.");
934
                break;
935
        }
936
        if (localChatTab != nullptr &&
937
            config.getBoolValue("showpickupchat"))
938
        {
939
            localChatTab->chatLog(gettext(msg),
940
                ChatMsgType::BY_SERVER,
941
                IgnoreRecord_false,
942
                TryRemoveColors_true);
943
        }
944
945
        if ((mMap != nullptr) && config.getBoolValue("showpickupparticle"))
946
        {
947
            // Show pickup notification
948
            addMessageToQueue(gettext(msg), UserColorId::PICKUP_INFO);
949
        }
950
    }
951
    else
952
    {
953
        std::string str;
954
#ifdef TMWA_SUPPORT
955
        if (Net::getNetworkType() == ServerType::TMWATHENA)
956
        {
957
            str = itemInfo.getName();
958
        }
959
        else
960
#endif  // TMWA_SUPPORT
961
        {
962
            str = itemInfo.getName(color);
963
        }
964
965
        if (config.getBoolValue("showpickupchat") && (localChatTab != nullptr))
966
        {
967
            // TRANSLATORS: %d is number,
968
            // [@@%d|%[email protected]@] - here player can see link to item
969
            localChatTab->chatLog(strprintf(ngettext("You picked up %d "
970
                "[@@%d|%[email protected]@].", "You picked up %d [@@%d|%[email protected]@].", amount),
971
                amount, itemInfo.getId(), str.c_str()),
972
                ChatMsgType::BY_SERVER,
973
                IgnoreRecord_false,
974
                TryRemoveColors_true);
975
        }
976
977
        if ((mMap != nullptr) && config.getBoolValue("showpickupparticle"))
978
        {
979
            // Show pickup notification
980
            if (amount > 1)
981
            {
982
                addMessageToQueue(strprintf("%d x %s", amount,
983
                    str.c_str()), UserColorId::PICKUP_INFO);
984
            }
985
            else
986
            {
987
                addMessageToQueue(str, UserColorId::PICKUP_INFO);
988
            }
989
        }
990
    }
991
}
992
993
2
int LocalPlayer::getAttackRange() const
994
{
995
2
    if (mAttackRange > -1)
996
    {
997
        return mAttackRange;
998
    }
999
1000
    const Item *const weapon = PlayerInfo::getEquipment(
1001
        EquipSlot::FIGHT1_SLOT);
1002
    if (weapon != nullptr)
1003
    {
1004
        const ItemInfo &info = weapon->getInfo();
1005
        return info.getAttackRange();
1006
    }
1007
    return 48;  // unarmed range
1008
}
1009
1010
bool LocalPlayer::withinAttackRange(const Being *const target,
1011
                                    const bool fixDistance,
1012
                                    const int addRange) const
1013
{
1014
    if (target == nullptr)
1015
        return false;
1016
1017
    int range = getAttackRange() + addRange;
1018
    int dx;
1019
    int dy;
1020
1021
    if (fixDistance && range == 1)
1022
        range = 2;
1023
1024
    dx = CAST_S32(abs(target->mX - mX));
1025
    dy = CAST_S32(abs(target->mY - mY));
1026
    return !(dx > range || dy > range);
1027
}
1028
1029
void LocalPlayer::setGotoTarget(Being *const target)
1030
{
1031
    if (target == nullptr)
1032
        return;
1033
1034
    mPickUpTarget = nullptr;
1035
    setTarget(target);
1036
    mGoingToTarget = true;
1037
    navigateTo(target->mX,
1038
        target->mY);
1039
}
1040
1041
void LocalPlayer::handleStatusEffect(const StatusEffect *const effect,
1042
                                     const int32_t effectId,
1043
                                     const Enable newStatus,
1044
                                     const IsStart start)
1045
{
1046
    Being::handleStatusEffect(effect,
1047
        effectId,
1048
        newStatus,
1049
        start);
1050
1051
    if (effect != nullptr)
1052
    {
1053
        effect->deliverMessage();
1054
        effect->playSFX();
1055
1056
        AnimatedSprite *const sprite = effect->getIcon();
1057
1058
        if (sprite == nullptr)
1059
        {
1060
            // delete sprite, if necessary
1061
            for (size_t i = 0; i < mStatusEffectIcons.size(); )
1062
            {
1063
                if (mStatusEffectIcons[i] == effectId)
1064
                {
1065
                    mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i);
1066
                    if (miniStatusWindow != nullptr)
1067
                        miniStatusWindow->eraseIcon(CAST_S32(i));
1068
                }
1069
                else
1070
                {
1071
                    i++;
1072
                }
1073
            }
1074
        }
1075
        else
1076
        {
1077
            // replace sprite or append
1078
            bool found = false;
1079
            const size_t sz = mStatusEffectIcons.size();
1080
            for (size_t i = 0; i < sz; i++)
1081
            {
1082
                if (mStatusEffectIcons[i] == effectId)
1083
                {
1084
                    if (miniStatusWindow != nullptr)
1085
                        miniStatusWindow->setIcon(CAST_S32(i), sprite);
1086
                    found = true;
1087
                    break;
1088
                }
1089
            }
1090
1091
            if (!found)
1092
            {   // add new
1093
                const int offset = CAST_S32(mStatusEffectIcons.size());
1094
                if (miniStatusWindow != nullptr)
1095
                    miniStatusWindow->setIcon(offset, sprite);
1096
                mStatusEffectIcons.push_back(effectId);
1097
            }
1098
        }
1099
    }
1100
}
1101
1102
void LocalPlayer::addMessageToQueue(const std::string &message,
1103
                                    const UserColorIdT color)
1104
{
1105
    if (mMessages.size() < 20)
1106
        mMessages.push_back(MessagePair(message, color));
1107
}
1108
1109
void LocalPlayer::optionChanged(const std::string &value)
1110
{
1111
    if (value == "showownname")
1112
    {
1113
        setShowName(config.getBoolValue("showownname"));
1114
    }
1115
    else if (value == "targetDeadPlayers")
1116
    {
1117
        mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers");
1118
    }
1119
    else if (value == "enableBuggyServers")
1120
    {
1121
        mIsServerBuggy = serverConfig.getBoolValue("enableBuggyServers");
1122
    }
1123
    else if (value == "syncPlayerMove")
1124
    {
1125
        mSyncPlayerMove = config.getBoolValue("syncPlayerMove");
1126
    }
1127
    else if (value == "syncPlayerMoveDistance")
1128
    {
1129
        mSyncPlayerMoveDistance = config.getIntValue("syncPlayerMoveDistance");
1130
    }
1131
    else if (value == "drawPath")
1132
    {
1133
        mDrawPath = config.getBoolValue("drawPath");
1134
    }
1135
    else if (value == "serverAttack")
1136
    {
1137
        mServerAttack = fromBool(config.getBoolValue("serverAttack"), Keep);
1138
    }
1139
    else if (value == "attackMoving")
1140
    {
1141
        mAttackMoving = config.getBoolValue("attackMoving");
1142
    }
1143
    else if (value == "attackNext")
1144
    {
1145
        mAttackNext = config.getBoolValue("attackNext");
1146
    }
1147
    else if (value == "showJobExp")
1148
    {
1149
        mShowJobExp = config.getBoolValue("showJobExp");
1150
    }
1151
    else if (value == "enableAdvert")
1152
    {
1153
        mEnableAdvert = config.getBoolValue("enableAdvert");
1154
    }
1155
    else if (value == "tradebot")
1156
    {
1157
        mTradebot = config.getBoolValue("tradebot");
1158
    }
1159
    else if (value == "targetOnlyReachable")
1160
    {
1161
        mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable");
1162
    }
1163
    else if (value == "showserverpos")
1164
    {
1165
        mShowServerPos = config.getBoolValue("showserverpos");
1166
    }
1167
    else if (value == "visiblenames")
1168
    {
1169
        mVisibleNames = static_cast<VisibleName::Type>(
1170
            config.getIntValue("visiblenames"));
1171
    }
1172
}
1173
1174
void LocalPlayer::addJobMessage(const int64_t change)
1175
{
1176
    if (change != 0 && mMessages.size() < 20)
1177
    {
1178
        const std::string xpStr = toString(CAST_U64(change));
1179
        if (!mMessages.empty())
1180
        {
1181
            MessagePair pair = mMessages.back();
1182
            // TRANSLATORS: this is normal experience
1183
            if (pair.first.find(strprintf(" %s", _("xp"))) ==
1184
                // TRANSLATORS: this is normal experience
1185
                pair.first.size() - strlen(_("xp")) - 1)
1186
            {
1187
                mMessages.pop_back();
1188
                pair.first.append(strprintf(", %s %s",
1189
                    xpStr.c_str(),
1190
                    // TRANSLATORS: this is job experience
1191
                    _("job")));
1192
                mMessages.push_back(pair);
1193
            }
1194
            else
1195
            {
1196
                addMessageToQueue(strprintf("%s %s",
1197
                    xpStr.c_str(),
1198
                    // TRANSLATORS: this is job experience
1199
                    _("job")),
1200
                    UserColorId::EXP_INFO);
1201
            }
1202
        }
1203
        else
1204
        {
1205
            addMessageToQueue(strprintf("%s %s",
1206
                xpStr.c_str(),
1207
                // TRANSLATORS: this is job experience
1208
                _("job")),
1209
                UserColorId::EXP_INFO);
1210
        }
1211
    }
1212
}
1213
1214
void LocalPlayer::addXpMessage(const int64_t change)
1215
{
1216
    if (change != 0 && mMessages.size() < 20)
1217
    {
1218
        addMessageToQueue(strprintf("%s %s",
1219
            toString(CAST_U64(change)).c_str(),
1220
            // TRANSLATORS: get xp message
1221
            _("xp")),
1222
            UserColorId::EXP_INFO);
1223
    }
1224
}
1225
1226
void LocalPlayer::addHomunXpMessage(const int change)
1227
{
1228
    if (change != 0 && mMessages.size() < 20)
1229
    {
1230
        addMessageToQueue(strprintf("%s %d %s",
1231
            // TRANSLATORS: get homunculus xp message
1232
            _("Homun"),
1233
            change,
1234
            // TRANSLATORS: get xp message
1235
            _("xp")),
1236
            UserColorId::EXP_INFO);
1237
    }
1238
}
1239
1240
void LocalPlayer::addHpMessage(const int change)
1241
{
1242
    if (change != 0 && mMessages.size() < 20)
1243
    {
1244
        // TRANSLATORS: get hp message
1245
        addMessageToQueue(strprintf("%d %s", change, _("hp")),
1246
            UserColorId::EXP_INFO);
1247
    }
1248
}
1249
1250
void LocalPlayer::addSpMessage(const int change)
1251
{
1252
    if (change != 0 && mMessages.size() < 20)
1253
    {
1254
        // TRANSLATORS: get hp message
1255
        addMessageToQueue(strprintf("%d %s", change, _("mana")),
1256
            UserColorId::EXP_INFO);
1257
    }
1258
}
1259
1260
void LocalPlayer::attributeChanged(const AttributesT id,
1261
                                   const int64_t oldVal,
1262
                                   const int64_t newVal)
1263
{
1264
    PRAGMA45(GCC diagnostic push)
1265
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
1266
    switch (id)
1267
    {
1268
        case Attributes::PLAYER_EXP:
1269
        {
1270
            if (Net::getNetworkType() != ServerType::TMWATHENA)
1271
                break;
1272
            if (oldVal > newVal)
1273
                break;
1274
1275
            const int change = CAST_S32(newVal - oldVal);
1276
            addXpMessage(change);
1277
            break;
1278
        }
1279
        case Attributes::PLAYER_BASE_LEVEL:
1280
            mLevel = CAST_S32(newVal);
1281
            break;
1282
        case Attributes::PLAYER_HP:
1283
            if (oldVal != 0 && newVal == 0)
1284
                PlayerDeathListener::distributeEvent();
1285
            break;
1286
        case Attributes::PLAYER_JOB_EXP:
1287
        {
1288
            if (!mShowJobExp ||
1289
                Net::getNetworkType() != ServerType::TMWATHENA)
1290
            {
1291
                return;
1292
            }
1293
            if (oldVal > newVal ||
1294
                PlayerInfo::getAttribute(
1295
                Attributes::PLAYER_JOB_EXP_NEEDED) == 0)
1296
            {
1297
                return;
1298
            }
1299
            const int32_t change = CAST_S32(newVal - oldVal);
1300
            addJobMessage(change);
1301
            break;
1302
        }
1303
        default:
1304
            break;
1305
    }
1306
    PRAGMA45(GCC diagnostic pop)
1307
}
1308
1309
void LocalPlayer::move(const int dX, const int dY)
1310
{
1311
    mPickUpTarget = nullptr;
1312
    setDestination(mX + dX, mY + dY);
1313
}
1314
1315
void LocalPlayer::moveToTarget(int dist)
1316
{
1317
    bool gotPos(false);
1318
    Path debugPath;
1319
1320
    size_t limit(0);
1321
1322
    if (dist == -1)
1323
    {
1324
        dist = settings.moveToTargetType;
1325
        if (dist != 0)
1326
        {
1327
            const bool broken = (Net::getNetworkType() ==
1328
                ServerType::TMWATHENA);
1329
            switch (dist)
1330
            {
1331
                case 10:
1332
                    dist = mAttackRange;
1333
                    if (dist == 1 && broken)
1334
                        dist = 2;
1335
                    break;
1336
                case 11:
1337
                    dist = mAttackRange - 1;
1338
                    if (dist < 1)
1339
                        dist = 1;
1340
                    if (dist == 1 && broken)
1341
                        dist = 2;
1342
                    break;
1343
                default:
1344
                    break;
1345
            }
1346
        }
1347
    }
1348
1349
    if (mTarget != nullptr)
1350
    {
1351
        if (mMap != nullptr)
1352
        {
1353
            debugPath = mMap->findPath(
1354
                (mPixelX - mapTileSize / 2) / mapTileSize,
1355
                (mPixelY - mapTileSize) / mapTileSize,
1356
                mTarget->mX,
1357
                mTarget->mY,
1358
                getBlockWalkMask(),
1359
                0);
1360
        }
1361
1362
        const size_t sz = debugPath.size();
1363
        if (sz < CAST_SIZE(dist))
1364
            return;
1365
        limit = CAST_S32(sz) - dist;
1366
        gotPos = true;
1367
    }
1368
    else if ((mNavigateX != 0) || (mNavigateY != 0))
1369
    {
1370
        debugPath = mNavigatePath;
1371
        limit = dist;
1372
        gotPos = true;
1373
    }
1374
1375
    if (gotPos)
1376
    {
1377
        if (dist == 0)
1378
        {
1379
            if (mTarget != nullptr)
1380
                navigateTo(mTarget->mX, mTarget->mY);
1381
        }
1382
        else
1383
        {
1384
            Position pos(0, 0);
1385
            size_t f = 0;
1386
1387
            for (Path::const_iterator i = debugPath.begin(),
1388
                 i_fend = debugPath.end();
1389
                 i != i_fend && f < limit; ++i, f++)
1390
            {
1391
                pos = (*i);
1392
            }
1393
            navigateTo(pos.x, pos.y);
1394
        }
1395
    }
1396
    else if ((mLastTargetX != 0) || (mLastTargetY != 0))
1397
    {
1398
        navigateTo(mLastTargetX, mLastTargetY);
1399
    }
1400
}
1401
1402
void LocalPlayer::moveToHome()
1403
{
1404
    mPickUpTarget = nullptr;
1405
    if ((mX != mCrossX || mY != mCrossY) && (mCrossX != 0) && (mCrossY != 0))
1406
    {
1407
        setDestination(mCrossX, mCrossY);
1408
    }
1409
    else if (mMap != nullptr)
1410
    {
1411
        const std::map<std::string, Vector>::const_iterator iter =
1412
            mHomes.find(mMap->getProperty("_realfilename", std::string()));
1413
1414
        if (iter != mHomes.end())
1415
        {
1416
            const Vector pos = mHomes[(*iter).first];
1417
            if (mX == pos.x && mY == pos.y)
1418
            {
1419
                playerHandler->setDestination(
1420
                        CAST_S32(pos.x),
1421
                        CAST_S32(pos.y),
1422
                        CAST_S32(mDirection));
1423
            }
1424
            else
1425
            {
1426
                navigateTo(CAST_S32(pos.x), CAST_S32(pos.y));
1427
            }
1428
        }
1429
    }
1430
}
1431
1432
void LocalPlayer::changeEquipmentBeforeAttack(const Being *const target) const
1433
{
1434
    if (settings.attackWeaponType == 1
1435
        || (target == nullptr)
1436
        || (PlayerInfo::getInventory() == nullptr))
1437
    {
1438
        return;
1439
    }
1440
1441
    bool allowSword = false;
1442
    const int dx = target->mX - mX;
1443
    const int dy = target->mY - mY;
1444
    const Item *item = nullptr;
1445
1446
    if (dx * dx + dy * dy > 80)
1447
        return;
1448
1449
    if (dx * dx + dy * dy < 8)
1450
        allowSword = true;
1451
1452
    const Inventory *const inv = PlayerInfo::getInventory();
1453
    if (inv == nullptr)
1454
        return;
1455
1456
    // if attack distance for sword
1457
    if (allowSword)
1458
    {
1459
        // searching swords
1460
        const WeaponsInfos &swords = WeaponsDB::getSwords();
1461
        FOR_EACH (WeaponsInfosIter, it, swords)
1462
        {
1463
            item = inv->findItem(*it, ItemColor_zero);
1464
            if (item != nullptr)
1465
                break;
1466
        }
1467
1468
        // no swords
1469
        if (item == nullptr)
1470
            return;
1471
1472
        // if sword not equiped
1473
        if (item->isEquipped() == Equipped_false)
1474
            PlayerInfo::equipItem(item, Sfx_true);
1475
1476
        // if need equip shield too
1477
        if (settings.attackWeaponType == 3)
1478
        {
1479
            // searching shield
1480
            const WeaponsInfos &shields = WeaponsDB::getShields();
1481
            FOR_EACH (WeaponsInfosIter, it, shields)
1482
            {
1483
                item = inv->findItem(*it, ItemColor_zero);
1484
                if (item != nullptr)
1485
                    break;
1486
            }
1487
            if ((item != nullptr) && item->isEquipped() == Equipped_false)
1488
                PlayerInfo::equipItem(item, Sfx_true);
1489
        }
1490
    }
1491
    // big distance. allowed only bow
1492
    else
1493
    {
1494
        // searching bow
1495
        const WeaponsInfos &bows = WeaponsDB::getBows();
1496
        FOR_EACH (WeaponsInfosIter, it, bows)
1497
        {
1498
            item = inv->findItem(*it, ItemColor_zero);
1499
            if (item != nullptr)
1500
                break;
1501
        }
1502
1503
        // no bow
1504
        if (item == nullptr)
1505
            return;
1506
1507
        if (item->isEquipped() == Equipped_false)
1508
            PlayerInfo::equipItem(item, Sfx_true);
1509
    }
1510
}
1511
1512
bool LocalPlayer::isReachable(Being *const being,
1513
                              const int maxCost)
1514
{
1515
    if ((being == nullptr) || (mMap == nullptr))
1516
        return false;
1517
1518
    if (being->getReachable() == Reachable::REACH_NO)
1519
        return false;
1520
1521
    if (being->mX == mX &&
1522
        being->mY == mY)
1523
    {
1524
        being->setDistance(0);
1525
        being->setReachable(Reachable::REACH_YES);
1526
        return true;
1527
    }
1528
    else if (being->mX - 1 <= mX &&
1529
             being->mX + 1 >= mX &&
1530
             being->mY - 1 <= mY &&
1531
             being->mY + 1 >= mY)
1532
    {
1533
        being->setDistance(1);
1534
        being->setReachable(Reachable::REACH_YES);
1535
        return true;
1536
    }
1537
1538
    const Path debugPath = mMap->findPath(
1539
        (mPixelX - mapTileSize / 2) / mapTileSize,
1540
        (mPixelY - mapTileSize) / mapTileSize,
1541
        being->mX,
1542
        being->mY,
1543
        getBlockWalkMask(),
1544
        maxCost);
1545
1546
    being->setDistance(CAST_S32(debugPath.size()));
1547
    if (!debugPath.empty())
1548
    {
1549
        being->setReachable(Reachable::REACH_YES);
1550
        return true;
1551
    }
1552
    being->setReachable(Reachable::REACH_NO);
1553
    return false;
1554
}
1555
1556
bool LocalPlayer::isReachable(const int x, const int y,
1557
                              const bool allowCollision) const
1558
{
1559
    const WalkLayer *const walk = mMap->getWalkLayer();
1560
    if (walk == nullptr)
1561
        return false;
1562
    int num = walk->getDataAt(x, y);
1563
    if (allowCollision && num < 0)
1564
        num = -num;
1565
1566
    return walk->getDataAt(mX, mY) == num;
1567
}
1568
1569
bool LocalPlayer::pickUpItems(int pickUpType)
1570
{
1571
    if (actorManager == nullptr)
1572
        return false;
1573
1574
    bool status = false;
1575
    int x = mX;
1576
    int y = mY;
1577
1578
    // first pick up item on player position
1579
    FloorItem *item =
1580
        actorManager->findItem(x, y);
1581
    if (item != nullptr)
1582
        status = pickUp(item);
1583
1584
    if (pickUpType == 0)
1585
        pickUpType = settings.pickUpType;
1586
1587
    if (pickUpType == 0)
1588
        return status;
1589
1590
    int x1;
1591
    int y1;
1592
    int x2;
1593
    int y2;
1594
    switch (pickUpType)
1595
    {
1596
        case 1:
1597
            switch (mDirection)
1598
            {
1599
                case BeingDirection::UP   : --y; break;
1600
                case BeingDirection::DOWN : ++y; break;
1601
                case BeingDirection::LEFT : --x; break;
1602
                case BeingDirection::RIGHT: ++x; break;
1603
                default: break;
1604
            }
1605
            item = actorManager->findItem(x, y);
1606
            if (item != nullptr)
1607
                status = pickUp(item);
1608
            break;
1609
        case 2:
1610
            switch (mDirection)
1611
            {
1612
                case BeingDirection::UP:
1613
                    x1 = x - 1; y1 = y - 1; x2 = x + 1; y2 = y; break;
1614
                case BeingDirection::DOWN:
1615
                    x1 = x - 1; y1 = y; x2 = x + 1; y2 = y + 1; break;
1616
                case BeingDirection::LEFT:
1617
                    x1 = x - 1; y1 = y - 1; x2 = x; y2 = y + 1; break;
1618
                case BeingDirection::RIGHT:
1619
                    x1 = x; y1 = y - 1; x2 = x + 1; y2 = y + 1; break;
1620
                default:
1621
                    x1 = x; x2 = x; y1 = y; y2 = y; break;
1622
            }
1623
            if (actorManager->pickUpAll(x1, y1, x2, y2, false))
1624
                status = true;
1625
            break;
1626
        case 3:
1627
            if (actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1628
                status = true;
1629
            break;
1630
1631
        case 4:
1632
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1633
            {
1634
                if (actorManager->pickUpNearest(x, y, 4))
1635
                    status = true;
1636
            }
1637
            else
1638
            {
1639
                status = true;
1640
            }
1641
            break;
1642
1643
        case 5:
1644
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1645
            {
1646
                if (actorManager->pickUpNearest(x, y, 8))
1647
                    status = true;
1648
            }
1649
            else
1650
            {
1651
                status = true;
1652
            }
1653
            break;
1654
1655
        case 6:
1656
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1657
            {
1658
                if (actorManager->pickUpNearest(x, y, 90))
1659
                    status = true;
1660
            }
1661
            else
1662
            {
1663
                status = true;
1664
            }
1665
            break;
1666
1667
        default:
1668
            break;
1669
    }
1670
    return status;
1671
}
1672
1673
1674
void LocalPlayer::moveByDirection(const unsigned char dir)
1675
{
1676
    int dx = 0;
1677
    int dy = 0;
1678
    if ((dir & BeingDirection::UP) != 0)
1679
        dy--;
1680
    if ((dir & BeingDirection::DOWN) != 0)
1681
        dy++;
1682
    if ((dir & BeingDirection::LEFT) != 0)
1683
        dx--;
1684
    if ((dir & BeingDirection::RIGHT) != 0)
1685
        dx++;
1686
    move(dx, dy);
1687
}
1688
1689
void LocalPlayer::specialMove(const unsigned char direction)
1690
{
1691
    if ((direction != 0U) && ((mNavigateX != 0) || (mNavigateY != 0)))
1692
        navigateClean();
1693
1694
    if ((direction != 0U) && (settings.moveType >= 2
1695
        && settings.moveType <= 4))
1696
    {
1697
        if (mAction == BeingAction::MOVE)
1698
            return;
1699
1700
        unsigned int max;
1701
1702
        if (settings.moveType == 2)
1703
            max = 5;
1704
        else if (settings.moveType == 4)
1705
            max = 1;
1706
        else
1707
            max = 3;
1708
1709
        if (getMoveState() < max)
1710
        {
1711
            moveByDirection(direction);
1712
            mMoveState ++;
1713
        }
1714
        else
1715
        {
1716
            mMoveState = 0;
1717
            crazyMoves->crazyMove();
1718
        }
1719
    }
1720
    else
1721
    {
1722
        setWalkingDir(direction);
1723
    }
1724
}
1725
1726
#ifdef TMWA_SUPPORT
1727
void LocalPlayer::magicAttack() const
1728
{
1729
    if (Net::getNetworkType() != ServerType::TMWATHENA)
1730
        return;
1731
    if (chatWindow == nullptr ||
1732
        !isAlive() ||
1733
        !playerHandler->canUseMagic())
1734
    {
1735
        return;
1736
    }
1737
1738
    switch (settings.magicAttackType)
1739
    {
1740
        // flar W00
1741
        case 0:
1742
            tryMagic("#flar", 1, 0, 10);
1743
            break;
1744
        // chiza W01
1745
        case 1:
1746
            tryMagic("#chiza", 1, 0,  9);
1747
            break;
1748
        // ingrav W10
1749
        case 2:
1750
            tryMagic("#ingrav", 2, 2,  20);
1751
            break;
1752
        // frillyar W11
1753
        case 3:
1754
            tryMagic("#frillyar", 2, 2, 25);
1755
            break;
1756
        // upmarmu W12
1757
        case 4:
1758
            tryMagic("#upmarmu", 2, 2, 20);
1759
            break;
1760
        default:
1761
            break;
1762
    }
1763
}
1764
1765
void LocalPlayer::tryMagic(const std::string &spell, const int baseMagic,
1766
                           const int schoolMagic, const int mana)
1767
{
1768
    if (chatWindow == nullptr)
1769
        return;
1770
1771
    if (PlayerInfo::getSkillLevel(340) >= baseMagic
1772
        && PlayerInfo::getSkillLevel(342) >= schoolMagic)
1773
    {
1774
        if (PlayerInfo::getAttribute(Attributes::PLAYER_MP) >= mana)
1775
        {
1776
            if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
1777
                return;
1778
1779
            chatWindow->localChatInput(spell);
1780
        }
1781
    }
1782
}
1783
#endif  // TMWA_SUPPORT
1784
1785
94
void LocalPlayer::loadHomes()
1786
{
1787
188
    std::string buf;
1788


846
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1789
1790

188
    while (ss >> buf)
1791
    {
1792
        Vector pos;
1793
        ss >> pos.x;
1794
        ss >> pos.y;
1795
        mHomes[buf] = pos;
1796
    }
1797
94
}
1798
1799
94
void LocalPlayer::setMap(Map *const map)
1800
{
1801
    BLOCK_START("LocalPlayer::setMap")
1802
94
    if (map != nullptr)
1803
    {
1804
        if (socialWindow != nullptr)
1805
            socialWindow->updateActiveList();
1806
    }
1807
94
    navigateClean();
1808
94
    mCrossX = 0;
1809
94
    mCrossY = 0;
1810
1811
94
    Being::setMap(map);
1812
94
    updateNavigateList();
1813
    BLOCK_END("LocalPlayer::setMap")
1814
94
}
1815
1816
void LocalPlayer::setHome()
1817
{
1818
    if ((mMap == nullptr) || (socialWindow == nullptr))
1819
        return;
1820
1821
    SpecialLayer *const specialLayer = mMap->getSpecialLayer();
1822
1823
    if (specialLayer == nullptr)
1824
        return;
1825
1826
    const std::string key = mMap->getProperty("_realfilename", std::string());
1827
    Vector pos = mHomes[key];
1828
1829
    if (mAction == BeingAction::SIT)
1830
    {
1831
        const std::map<std::string, Vector>::const_iterator
1832
            iter = mHomes.find(key);
1833
1834
        if (iter != mHomes.end())
1835
        {
1836
            socialWindow->removePortal(CAST_S32(pos.x),
1837
                CAST_S32(pos.y));
1838
        }
1839
1840
        if (iter != mHomes.end() && mX == CAST_S32(pos.x)
1841
            && mY == CAST_S32(pos.y))
1842
        {
1843
            mMap->updatePortalTile("",
1844
                MapItemType::EMPTY,
1845
                CAST_S32(pos.x),
1846
                CAST_S32(pos.y),
1847
                true);
1848
1849
            mHomes.erase(key);
1850
            socialWindow->removePortal(CAST_S32(pos.x),
1851
                CAST_S32(pos.y));
1852
        }
1853
        else
1854
        {
1855
            if (iter != mHomes.end())
1856
            {
1857
                specialLayer->setTile(CAST_S32(pos.x),
1858
                    CAST_S32(pos.y), MapItemType::EMPTY);
1859
                specialLayer->updateCache();
1860
            }
1861
1862
            pos.x = static_cast<float>(mX);
1863
            pos.y = static_cast<float>(mY);
1864
            mHomes[key] = pos;
1865
            mMap->updatePortalTile("home",
1866
                MapItemType::HOME,
1867
                mX,
1868
                mY,
1869
                true);
1870
            socialWindow->addPortal(mX, mY);
1871
        }
1872
        MapItem *const mapItem = specialLayer->getTile(mX, mY);
1873
        if (mapItem != nullptr)
1874
        {
1875
            const int idx = socialWindow->getPortalIndex(mX, mY);
1876
            mapItem->setName(KeyboardConfig::getKeyShortString(
1877
                OutfitWindow::keyName(idx)));
1878
        }
1879
        saveHomes();
1880
    }
1881
    else
1882
    {
1883
        MapItem *mapItem = specialLayer->getTile(mX, mY);
1884
        int type = 0;
1885
1886
        const std::map<std::string, Vector>::iterator iter = mHomes.find(key);
1887
        if (iter != mHomes.end() && mX == pos.x && mY == pos.y)
1888
        {
1889
            mHomes.erase(key);
1890
            saveHomes();
1891
        }
1892
1893
        if ((mapItem == nullptr) || mapItem->getType() == MapItemType::EMPTY)
1894
        {
1895
            if ((mDirection & BeingDirection::UP) != 0)
1896
                type = MapItemType::ARROW_UP;
1897
            else if ((mDirection & BeingDirection::LEFT) != 0)
1898
                type = MapItemType::ARROW_LEFT;
1899
            else if ((mDirection & BeingDirection::DOWN) != 0)
1900
                type = MapItemType::ARROW_DOWN;
1901
            else if ((mDirection & BeingDirection::RIGHT) != 0)
1902
                type = MapItemType::ARROW_RIGHT;
1903
        }
1904
        else
1905
        {
1906
            type = MapItemType::EMPTY;
1907
        }
1908
        mMap->updatePortalTile("",
1909
            type,
1910
            mX,
1911
            mY,
1912
            true);
1913
1914
        if (type != MapItemType::EMPTY)
1915
        {
1916
            socialWindow->addPortal(mX, mY);
1917
            mapItem = specialLayer->getTile(mX, mY);
1918
            if (mapItem != nullptr)
1919
            {
1920
                const int idx = socialWindow->getPortalIndex(mX, mY);
1921
                mapItem->setName(KeyboardConfig::getKeyShortString(
1922
                    OutfitWindow::keyName(idx)));
1923
            }
1924
        }
1925
        else
1926
        {
1927
            specialLayer->setTile(mX, mY, MapItemType::EMPTY);
1928
            specialLayer->updateCache();
1929
            socialWindow->removePortal(mX, mY);
1930
        }
1931
    }
1932
}
1933
1934
void LocalPlayer::saveHomes()
1935
{
1936
    std::stringstream ss;
1937
1938
    for (std::map<std::string, Vector>::const_iterator iter = mHomes.begin(),
1939
         iter_fend = mHomes.end();
1940
         iter != iter_fend;
1941
         ++iter)
1942
    {
1943
        const Vector &pos = (*iter).second;
1944
1945
        if (iter != mHomes.begin())
1946
            ss << " ";
1947
        ss << (*iter).first << " " << pos.x << " " << pos.y;
1948
    }
1949
1950
    serverConfig.setValue("playerHomes", ss.str());
1951
}
1952
1953
void LocalPlayer::pingRequest()
1954
{
1955
    const int time = tick_time;
1956
    if (mWaitPing == true && mPingSendTick != 0)
1957
    {
1958
        if (time >= mPingSendTick && (time - mPingSendTick) > 1000)
1959
            return;
1960
    }
1961
1962
    mPingSendTick = time;
1963
    mWaitPing = true;
1964
    beingHandler->requestNameById(getId());
1965
}
1966
1967
std::string LocalPlayer::getPingTime() const
1968
{
1969
    std::string str;
1970
    if (!mWaitPing)
1971
    {
1972
        if (mPingTime == 0)
1973
            str = "?";
1974
        else
1975
            str = toString(CAST_S32(mPingTime));
1976
    }
1977
    else
1978
    {
1979
        time_t time = tick_time;
1980
        if (time > mPingSendTick)
1981
            time -= mPingSendTick;
1982
        else
1983
            time += MAX_TICK_VALUE - mPingSendTick;
1984
        if (time <= mPingTime)
1985
            time = mPingTime;
1986
        if (mPingTime != time)
1987
            str = strprintf("%d (%d)", CAST_S32(mPingTime), CAST_S32(time));
1988
        else
1989
            str = toString(CAST_S32(time));
1990
    }
1991
    return str;
1992
}
1993
1994
void LocalPlayer::pingResponse()
1995
{
1996
    if (mWaitPing == true && mPingSendTick > 0)
1997
    {
1998
        mWaitPing = false;
1999
        const int time = tick_time;
2000
        if (time < mPingSendTick)
2001
        {
2002
            mPingSendTick = 0;
2003
            mPingTime = 0;
2004
        }
2005
        else
2006
        {
2007
            mPingTime = (time - mPingSendTick) * 10;
2008
        }
2009
    }
2010
}
2011
2012
void LocalPlayer::tryPingRequest()
2013
{
2014
    if (mPingSendTick == 0 || tick_time < mPingSendTick
2015
        || (tick_time - mPingSendTick) > 200)
2016
    {
2017
        pingRequest();
2018
    }
2019
}
2020
2021
2022
void LocalPlayer::setAway(const std::string &message) const
2023
{
2024
    setAfkMessage(message);
2025
    GameModifiers::changeAwayMode(true);
2026
    updateStatus();
2027
}
2028
2029
void LocalPlayer::setAfkMessage(std::string message)
2030
{
2031
    if (!message.empty())
2032
    {
2033
        if (message.size() > 4 && message.substr(0, 4) == "/me ")
2034
        {
2035
            message = message.substr(4);
2036
            config.setValue("afkFormat", 1);
2037
        }
2038
        else
2039
        {
2040
            config.setValue("afkFormat", 0);
2041
        }
2042
        serverConfig.setValue("afkMessage", message);
2043
    }
2044
}
2045
2046
void LocalPlayer::setPseudoAway(const std::string &message)
2047
{
2048
    setAfkMessage(message);
2049
    settings.pseudoAwayMode = !settings.pseudoAwayMode;
2050
}
2051
2052
void LocalPlayer::afkRespond(ChatTab *const tab, const std::string &nick)
2053
{
2054
    if (settings.awayMode)
2055
    {
2056
        const time_t time = cur_time;
2057
        if (mAfkTime == 0 || time < mAfkTime
2058
            || time - mAfkTime > awayLimitTimer)
2059
        {
2060
            std::string str(serverConfig.getValue("afkMessage",
2061
                "I am away from keyboard."));
2062
            if (str.find("'NAME'") != std::string::npos)
2063
                replaceAll(str, "'NAME'", nick);
2064
2065
            std::string msg("*AFK*: " + str);
2066
2067
            if (config.getIntValue("afkFormat") == 1)
2068
                msg = "*" + msg + "*";
2069
2070
            if (tab == nullptr)
2071
            {
2072
                chatHandler->privateMessage(nick, msg);
2073
                if (localChatTab != nullptr)
2074
                {
2075
                    localChatTab->chatLog(std::string(mName).append(
2076
                        " : ").append(msg),
2077
                        ChatMsgType::ACT_WHISPER,
2078
                        IgnoreRecord_false,
2079
                        TryRemoveColors_true);
2080
                }
2081
            }
2082
            else
2083
            {
2084
                if (tab->getNoAway())
2085
                    return;
2086
                chatHandler->privateMessage(nick, msg);
2087
                tab->chatLog(mName, msg);
2088
            }
2089
            mAfkTime = time;
2090
        }
2091
    }
2092
}
2093
2094
bool LocalPlayer::navigateTo(const int x, const int y)
2095
{
2096
    if (mMap == nullptr)
2097
        return false;
2098
2099
    SpecialLayer *const tmpLayer = mMap->getTempLayer();
2100
    if (tmpLayer == nullptr)
2101
        return false;
2102
2103
    mShowNavigePath = true;
2104
    mOldX = mPixelX;
2105
    mOldY = mPixelY;
2106
    mOldTileX = mX;
2107
    mOldTileY = mY;
2108
    mNavigateX = x;
2109
    mNavigateY = y;
2110
    mNavigateId = BeingId_zero;
2111
2112
    mNavigatePath = mMap->findPath(
2113
        (mPixelX - mapTileSize / 2) / mapTileSize,
2114
        (mPixelY - mapTileSize) / mapTileSize,
2115
        x,
2116
        y,
2117
        getBlockWalkMask(),
2118
        0);
2119
2120
    if (mDrawPath)
2121
        tmpLayer->addRoad(mNavigatePath);
2122
    return !mNavigatePath.empty();
2123
}
2124
2125
188
void LocalPlayer::navigateClean()
2126
{
2127
188
    if (mMap == nullptr)
2128
        return;
2129
2130
    mShowNavigePath = false;
2131
    mOldX = 0;
2132
    mOldY = 0;
2133
    mOldTileX = 0;
2134
    mOldTileY = 0;
2135
    mNavigateX = 0;
2136
    mNavigateY = 0;
2137
    mNavigateId = BeingId_zero;
2138
2139
    mNavigatePath.clear();
2140
2141
    SpecialLayer *const tmpLayer = mMap->getTempLayer();
2142
    if (tmpLayer == nullptr)
2143
        return;
2144
2145
    tmpLayer->clean();
2146
}
2147
2148
void LocalPlayer::updateMusic() const
2149
{
2150
    if (mMap != nullptr)
2151
    {
2152
        std::string str = mMap->getObjectData(mX, mY, MapItemType::MUSIC);
2153
        if (str.empty())
2154
            str = mMap->getMusicFile();
2155
        if (str != soundManager.getCurrentMusicFile())
2156
        {
2157
            if (str.empty())
2158
                soundManager.fadeOutMusic(1000);
2159
            else
2160
                soundManager.fadeOutAndPlayMusic(str, 1000);
2161
        }
2162
    }
2163
}
2164
2165
void LocalPlayer::updateCoords()
2166
{
2167
    Being::updateCoords();
2168
2169
    // probably map not loaded.
2170
    if ((mPixelX == 0) || (mPixelY == 0))
2171
        return;
2172
2173
    if (mX != mOldTileX || mY != mOldTileY)
2174
    {
2175
        if (socialWindow != nullptr)
2176
            socialWindow->updatePortals();
2177
        PopupManager::hideBeingPopup();
2178
        updateMusic();
2179
    }
2180
2181
    if ((mMap != nullptr) && (mX != mOldTileX || mY != mOldTileY))
2182
    {
2183
        SpecialLayer *const tmpLayer = mMap->getTempLayer();
2184
        if (tmpLayer == nullptr)
2185
            return;
2186
2187
        const int x = (mPixelX - mapTileSize / 2) / mapTileSize;
2188
        const int y = (mPixelY - mapTileSize) / mapTileSize;
2189
        if (mNavigateId != BeingId_zero)
2190
        {
2191
            if (actorManager == nullptr)
2192
            {
2193
                navigateClean();
2194
                return;
2195
            }
2196
2197
            const Being *const being = actorManager
2198
                ->findBeing(mNavigateId);
2199
            if (being == nullptr)
2200
            {
2201
                navigateClean();
2202
                return;
2203
            }
2204
            mNavigateX = being->mX;
2205
            mNavigateY = being->mY;
2206
        }
2207
2208
        if (mNavigateX == x && mNavigateY == y)
2209
        {
2210
            navigateClean();
2211
            return;
2212
        }
2213
        for (Path::const_iterator i = mNavigatePath.begin(),
2214
             i_fend = mNavigatePath.end();
2215
             i != i_fend;
2216
             ++i)
2217
        {
2218
            if ((*i).x == mX && (*i).y == mY)
2219
            {
2220
                mNavigatePath.pop_front();
2221
                fixPos();
2222
                break;
2223
            }
2224
        }
2225
        if (mDrawPath && mShowNavigePath)
2226
        {
2227
            tmpLayer->clean();
2228
            tmpLayer->addRoad(mNavigatePath);
2229
        }
2230
    }
2231
    mOldX = mPixelX;
2232
    mOldY = mPixelY;
2233
    mOldTileX = mX;
2234
    mOldTileY = mY;
2235
}
2236
2237
void LocalPlayer::targetMoved() const
2238
{
2239
/*
2240
    if (mKeepAttacking)
2241
    {
2242
        if (mTarget && mServerAttack == Keep_true)
2243
        {
2244
            logger->log("LocalPlayer::targetMoved0");
2245
            if (!PacketLimiter::limitPackets(PacketType::PACKET_ATTACK))
2246
                return;
2247
            logger->log("LocalPlayer::targetMoved");
2248
            playerHandler->attack(mTarget->getId(), mServerAttack);
2249
        }
2250
    }
2251
*/
2252
}
2253
2254
int LocalPlayer::getPathLength(const Being *const being) const
2255
{
2256
    if ((mMap == nullptr) || (being == nullptr))
2257
        return 0;
2258
2259
    if (being->mX == mX && being->mY == mY)
2260
        return 0;
2261
2262
    if (being->mX - 1 <= mX &&
2263
        being->mX + 1 >= mX &&
2264
        being->mY - 1 <= mY &&
2265
        being->mY + 1 >= mY)
2266
    {
2267
        return 1;
2268
    }
2269
2270
    if (mTargetOnlyReachable)
2271
    {
2272
        const Path debugPath = mMap->findPath(
2273
            (mPixelX - mapTileSize / 2) / mapTileSize,
2274
            (mPixelY - mapTileSize) / mapTileSize,
2275
            being->mX,
2276
            being->mY,
2277
            getBlockWalkMask(),
2278
            0);
2279
        return CAST_S32(debugPath.size());
2280
    }
2281
2282
    const int dx = CAST_S32(abs(being->mX - mX));
2283
    const int dy = CAST_S32(abs(being->mY - mY));
2284
    if (dx > dy)
2285
        return dx;
2286
    return dy;
2287
}
2288
2289
int LocalPlayer::getAttackRange2() const
2290
{
2291
    int range = getAttackRange();
2292
    if (range == 1)
2293
        range = 2;
2294
    return range;
2295
}
2296
2297
void LocalPlayer::attack2(Being *const target,
2298
                          const bool keep,
2299
                          const bool dontChangeEquipment)
2300
{
2301
    if (!dontChangeEquipment && (target != nullptr))
2302
        changeEquipmentBeforeAttack(target);
2303
2304
    const bool broken = (Net::getNetworkType() == ServerType::TMWATHENA);
2305
2306
    // probably need cache getPathLength(target)
2307
    if ((target == nullptr ||
2308
        settings.attackType == 0 ||
2309
        settings.attackType == 3) ||
2310
        (withinAttackRange(target, broken, broken ? 1 : 0) &&
2311
        getPathLength(target) <= getAttackRange2()))
2312
    {
2313
        attack(target, keep, false);
2314
        if (settings.attackType == 2)
2315
        {
2316
            if (target == nullptr)
2317
            {
2318
                if (pickUpItems(0))
2319
                    return;
2320
            }
2321
            else
2322
            {
2323
                pickUpItems(3);
2324
            }
2325
        }
2326
    }
2327
    else if (mPickUpTarget == nullptr)
2328
    {
2329
        if (settings.attackType == 2)
2330
        {
2331
            if (pickUpItems(0))
2332
                return;
2333
        }
2334
        setTarget(target);
2335
        if (target->getType() != ActorType::Npc)
2336
        {
2337
            mKeepAttacking = true;
2338
            moveToTarget(-1);
2339
        }
2340
    }
2341
}
2342
2343
void LocalPlayer::setFollow(const std::string &player)
2344
{
2345
    mPlayerFollowed = player;
2346
    if (!mPlayerFollowed.empty())
2347
    {
2348
        // TRANSLATORS: follow command message
2349
        std::string msg = strprintf(_("Follow: %s"), player.c_str());
2350
        debugMsg(msg)
2351
    }
2352
    else
2353
    {
2354
        // TRANSLATORS: follow command message
2355
        debugMsg(_("Follow canceled"))
2356
    }
2357
}
2358
2359
void LocalPlayer::setImitate(const std::string &player)
2360
{
2361
    mPlayerImitated = player;
2362
    if (!mPlayerImitated.empty())
2363
    {
2364
        // TRANSLATORS: imitate command message
2365
        std::string msg = strprintf(_("Imitation: %s"), player.c_str());
2366
        debugMsg(msg)
2367
    }
2368
    else
2369
    {
2370
        // TRANSLATORS: imitate command message
2371
        debugMsg(_("Imitation canceled"))
2372
    }
2373
}
2374
2375
void LocalPlayer::cancelFollow()
2376
{
2377
    if (!mPlayerFollowed.empty())
2378
    {
2379
        // TRANSLATORS: cancel follow message
2380
        debugMsg(_("Follow canceled"))
2381
    }
2382
    if (!mPlayerImitated.empty())
2383
    {
2384
        // TRANSLATORS: cancel follow message
2385
        debugMsg(_("Imitation canceled"))
2386
    }
2387
    mPlayerFollowed.clear();
2388
    mPlayerImitated.clear();
2389
}
2390
2391
void LocalPlayer::imitateEmote(const Being *const being,
2392
                               const unsigned char action) const
2393
{
2394
    if (being == nullptr)
2395
        return;
2396
2397
    std::string player_imitated = getImitate();
2398
    if (!player_imitated.empty() && being->mName == player_imitated)
2399
        emote(action);
2400
}
2401
2402
void LocalPlayer::imitateAction(const Being *const being,
2403
                                const BeingActionT &action)
2404
{
2405
    if (being == nullptr)
2406
        return;
2407
2408
    if (!mPlayerImitated.empty() && being->mName == mPlayerImitated)
2409
    {
2410
        setAction(action, 0);
2411
        playerHandler->changeAction(action);
2412
    }
2413
}
2414
2415
void LocalPlayer::imitateDirection(const Being *const being,
2416
                                   const unsigned char dir)
2417
{
2418
    if (being == nullptr)
2419
        return;
2420
2421
    if (!mPlayerImitated.empty() && being->mName == mPlayerImitated)
2422
    {
2423
        if (!PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION))
2424
            return;
2425
2426
        if (settings.followMode == 2)
2427
        {
2428
            uint8_t dir2 = 0;
2429
            if ((dir & BeingDirection::LEFT) != 0)
2430
                dir2 |= BeingDirection::RIGHT;
2431
            else if ((dir & BeingDirection::RIGHT) != 0)
2432
                dir2 |= BeingDirection::LEFT;
2433
            if ((dir & BeingDirection::UP) != 0)
2434
                dir2 |= BeingDirection::DOWN;
2435
            else if ((dir & BeingDirection::DOWN) != 0)
2436
                dir2 |= BeingDirection::UP;
2437
2438
            setDirection(dir2);
2439
            playerHandler->setDirection(dir2);
2440
        }
2441
        else
2442
        {
2443
            setDirection(dir);
2444
            playerHandler->setDirection(dir);
2445
        }
2446
    }
2447
}
2448
2449
void LocalPlayer::imitateOutfit(const Being *const player,
2450
                                const int sprite) const
2451
{
2452
    if (player == nullptr)
2453
        return;
2454
2455
    if (settings.imitationMode == 1 &&
2456
        !mPlayerImitated.empty() &&
2457
        player->mName == mPlayerImitated)
2458
    {
2459
        if (sprite < 0 || sprite >= player->getNumberOfLayers())
2460
            return;
2461
2462
        const AnimatedSprite *const equipmentSprite
2463
            = dynamic_cast<const AnimatedSprite *>(
2464
            player->mSprites[sprite]);
2465
2466
        if (equipmentSprite != nullptr)
2467
        {
2468
//            logger->log("have equipmentSprite");
2469
            const Inventory *const inv = PlayerInfo::getInventory();
2470
            if (inv == nullptr)
2471
                return;
2472
2473
            const std::string &path = equipmentSprite->getIdPath();
2474
            if (path.empty())
2475
                return;
2476
2477
//            logger->log("idPath: " + path);
2478
            const Item *const item = inv->findItemBySprite(path,
2479
                player->getGender(), player->getSubType());
2480
            if ((item != nullptr) && item->isEquipped() == Equipped_false)
2481
                PlayerInfo::equipItem(item, Sfx_false);
2482
        }
2483
        else
2484
        {
2485
//            logger->log("have unequip %d", sprite);
2486
            const int equipmentSlot = inventoryHandler
2487
                ->convertFromServerSlot(sprite);
2488
//            logger->log("equipmentSlot: " + toString(equipmentSlot));
2489
            if (equipmentSlot == inventoryHandler->getProjectileSlot())
2490
                return;
2491
2492
            const Item *const item = PlayerInfo::getEquipment(equipmentSlot);
2493
            if (item != nullptr)
2494
            {
2495
//                logger->log("unequiping");
2496
                PlayerInfo::unequipItem(item, Sfx_false);
2497
            }
2498
        }
2499
    }
2500
}
2501
2502
void LocalPlayer::followMoveTo(const Being *const being,
2503
                               const int x, const int y)
2504
{
2505
    if ((being != nullptr) &&
2506
        !mPlayerFollowed.empty() &&
2507
        being->mName == mPlayerFollowed)
2508
    {
2509
        mPickUpTarget = nullptr;
2510
        navigateTo(x, y);
2511
    }
2512
}
2513
2514
void LocalPlayer::followMoveTo(const Being *const being,
2515
                               const int x1, const int y1,
2516
                               const int x2, const int y2)
2517
{
2518
    if (being == nullptr)
2519
        return;
2520
2521
    mPickUpTarget = nullptr;
2522
    if (!mPlayerFollowed.empty() &&
2523
        being->mName == mPlayerFollowed)
2524
    {
2525
        switch (settings.followMode)
2526
        {
2527
            case 0:
2528
                navigateTo(x1, y1);
2529
                setNextDest(x2, y2);
2530
                break;
2531
            case 1:
2532
                if (x1 != x2 || y1 != y2)
2533
                {
2534
                    navigateTo(mX + x2 - x1, mY + y2 - y1);
2535
                    setNextDest(mX + x2 - x1, mY + y2 - y1);
2536
                }
2537
                break;
2538
            case 2:
2539
                if (x1 != x2 || y1 != y2)
2540
                {
2541
                    navigateTo(mX + x1 - x2, mY + y1 - y2);
2542
                    setNextDest(mX + x1 - x2, mY + y1 - y2);
2543
                }
2544
                break;
2545
            case 3:
2546
                if (mTarget == nullptr ||
2547
                    mTarget->mName != mPlayerFollowed)
2548
                {
2549
                    if (actorManager != nullptr)
2550
                    {
2551
                        Being *const b = actorManager->findBeingByName(
2552
                            mPlayerFollowed, ActorType::Player);
2553
                        setTarget(b);
2554
                    }
2555
                }
2556
                moveToTarget(-1);
2557
                setNextDest(x2, y2);
2558
                break;
2559
            default:
2560
                break;
2561
        }
2562
    }
2563
}
2564
2565
void LocalPlayer::setNextDest(const int x, const int y)
2566
{
2567
    mNextDestX = x;
2568
    mNextDestY = y;
2569
}
2570
2571
bool LocalPlayer::allowAction()
2572
{
2573
    if (mIsServerBuggy)
2574
    {
2575
        if (mLastAction != -1)
2576
            return false;
2577
        mLastAction = tick_time;
2578
    }
2579
    return true;
2580
}
2581
2582
void LocalPlayer::fixPos()
2583
{
2584
    if ((mCrossX == 0) && (mCrossY == 0))
2585
        return;
2586
2587
    const int dx = (mX >= mCrossX) ? mX - mCrossX : mCrossX - mX;
2588
    const int dy = (mY >= mCrossY) ? mY - mCrossY : mCrossY - mY;
2589
    const int dist = dx > dy ? dx : dy;
2590
    const time_t time = cur_time;
2591
    const int maxDist = mSyncPlayerMove ? mSyncPlayerMoveDistance : 7;
2592
2593
    if (dist > maxDist)
2594
    {
2595
        mActivityTime = time;
2596
#ifdef ENABLEDEBUGLOG
2597
        logger->dlog(strprintf("Fix position from (%d,%d) to (%d,%d)",
2598
            mX, mY,
2599
            mCrossX, mCrossY));
2600
#endif
2601
        setTileCoords(mCrossX, mCrossY);
2602
/*
2603
        if (mNavigateX != 0 || mNavigateY != 0)
2604
        {
2605
#ifdef ENABLEDEBUGLOG
2606
            logger->dlog(strprintf("Renavigate to (%d,%d)",
2607
                mNavigateX, mNavigateY));
2608
#endif
2609
            navigateTo(mNavigateX, mNavigateY);
2610
        }
2611
*/
2612
// alternative way to fix, move to real position
2613
//        setDestination(mCrossX, mCrossY);
2614
    }
2615
}
2616
2617
void LocalPlayer::setTileCoords(const int x, const int y) restrict2
2618
{
2619
    Being::setTileCoords(x, y);
2620
    mCrossX = x;
2621
    mCrossY = y;
2622
}
2623
2624
void LocalPlayer::setRealPos(const int x, const int y)
2625
{
2626
    if (mMap == nullptr)
2627
        return;
2628
2629
    SpecialLayer *const layer = mMap->getTempLayer();
2630
    if (layer != nullptr)
2631
    {
2632
        bool cacheUpdated(false);
2633
        if (((mCrossX != 0) || (mCrossY != 0)) &&
2634
            (layer->getTile(mCrossX, mCrossY) != nullptr) &&
2635
            layer->getTile(mCrossX, mCrossY)->getType() == MapItemType::CROSS)
2636
        {
2637
            layer->setTile(mCrossX, mCrossY, MapItemType::EMPTY);
2638
            layer->updateCache();
2639
            cacheUpdated = true;
2640
        }
2641
2642
        if (mShowServerPos)
2643
        {
2644
            const MapItem *const mapItem = layer->getTile(x, y);
2645
2646
            if (mapItem == nullptr ||
2647
                mapItem->getType() == MapItemType::EMPTY)
2648
            {
2649
                if (mX != x && mY != y)
2650
                {
2651
                    layer->setTile(x, y, MapItemType::CROSS);
2652
                    if (cacheUpdated == false)
2653
                        layer->updateCache();
2654
                }
2655
            }
2656
        }
2657
2658
        if (mCrossX != x || mCrossY != y)
2659
        {
2660
            mCrossX = x;
2661
            mCrossY = y;
2662
            // +++ possible configuration option
2663
            fixPos();
2664
        }
2665
    }
2666
    if (mMap->isCustom())
2667
        mMap->setWalk(x, y);
2668
}
2669
2670
void LocalPlayer::fixAttackTarget()
2671
{
2672
    if ((mMap == nullptr) || (mTarget == nullptr))
2673
        return;
2674
2675
    if (settings.moveToTargetType == 11 || (settings.attackType == 0U)
2676
        || !config.getBoolValue("autofixPos"))
2677
    {
2678
        return;
2679
    }
2680
2681
    const Path debugPath = mMap->findPath(
2682
        (mPixelX - mapTileSize / 2) / mapTileSize,
2683
        (mPixelY - mapTileSize) / mapTileSize,
2684
        mTarget->mX,
2685
        mTarget->mY,
2686
        getBlockWalkMask(),
2687
        0);
2688
2689
    if (!debugPath.empty())
2690
    {
2691
        const Path::const_iterator i = debugPath.begin();
2692
        setDestination((*i).x, (*i).y);
2693
    }
2694
}
2695
2696
void LocalPlayer::respawn()
2697
{
2698
    navigateClean();
2699
}
2700
2701
1
int LocalPlayer::getLevel() const
2702
{
2703
1
    return PlayerInfo::getAttribute(Attributes::PLAYER_BASE_LEVEL);
2704
}
2705
2706
188
void LocalPlayer::updateNavigateList()
2707
{
2708
188
    if (mMap != nullptr)
2709
    {
2710
        const std::map<std::string, Vector>::const_iterator iter =
2711
            mHomes.find(mMap->getProperty("_realfilename", std::string()));
2712
2713
        if (iter != mHomes.end())
2714
        {
2715
            const Vector &pos = mHomes[(*iter).first];
2716
            if ((pos.x != 0.0F) && (pos.y != 0.0F))
2717
            {
2718
                mMap->addPortalTile("home", MapItemType::HOME,
2719
                    CAST_S32(pos.x), CAST_S32(pos.y));
2720
            }
2721
        }
2722
    }
2723
188
}
2724
2725
void LocalPlayer::failMove(const int x A_UNUSED,
2726
                           const int y A_UNUSED)
2727
{
2728
    fixPos();
2729
}
2730
2731
void LocalPlayer::waitFor(const std::string &nick)
2732
{
2733
    mWaitFor = nick;
2734
}
2735
2736
void LocalPlayer::checkNewName(Being *const being)
2737
{
2738
    if (being == nullptr)
2739
        return;
2740
2741
    const std::string &nick = being->mName;
2742
    if (being->getType() == ActorType::Player)
2743
    {
2744
        const Guild *const guild = getGuild();
2745
        if (guild != nullptr)
2746
        {
2747
            const GuildMember *const gm = guild->getMember(nick);
2748
            if (gm != nullptr)
2749
            {
2750
                const int level = gm->getLevel();
2751
                if (level > 1 && being->getLevel() != level)
2752
                {
2753
                    being->setLevel(level);
2754
                    being->updateName();
2755
                }
2756
            }
2757
        }
2758
        if (chatWindow != nullptr)
2759
        {
2760
            WhisperTab *const tab = chatWindow->getWhisperTab(nick);
2761
            if (tab != nullptr)
2762
                tab->setWhisperTabColors();
2763
        }
2764
    }
2765
2766
    if (!mWaitFor.empty() && mWaitFor == nick)
2767
    {
2768
        // TRANSLATORS: wait player/monster message
2769
        debugMsg(strprintf(_("You see %s"), mWaitFor.c_str()))
2770
        soundManager.playGuiSound(SOUND_INFO);
2771
        mWaitFor.clear();
2772
    }
2773
}
2774
2775
unsigned char LocalPlayer::getBlockWalkMask() const
2776
{
2777
    // for now blocking all types of collisions
2778
    return BlockMask::WALL |
2779
        BlockMask::AIR |
2780
        BlockMask::WATER |
2781
        BlockMask::PLAYERWALL;
2782
}
2783
2784
void LocalPlayer::removeHome()
2785
{
2786
    if (mMap == nullptr)
2787
        return;
2788
2789
    const std::string key = mMap->getProperty("_realfilename", std::string());
2790
    const std::map<std::string, Vector>::iterator iter = mHomes.find(key);
2791
2792
    if (iter != mHomes.end())
2793
        mHomes.erase(key);
2794
}
2795
2796
void LocalPlayer::stopAdvert()
2797
{
2798
    mBlockAdvert = true;
2799
}
2800
2801
bool LocalPlayer::checAttackPermissions(const Being *const target)
2802
{
2803
    if (target == nullptr)
2804
        return false;
2805
2806
    switch (settings.pvpAttackType)
2807
    {
2808
        case 0:
2809
            return true;
2810
        case 1:
2811
            return !(playerRelations.getRelation(target->mName)
2812
                 == Relation::FRIEND);
2813
        case 2:
2814
            return playerRelations.checkBadRelation(target->mName);
2815
        default:
2816
        case 3:
2817
            return false;
2818
    }
2819
}
2820
2821
void LocalPlayer::updateStatus() const
2822
{
2823
    if (serverFeatures->havePlayerStatusUpdate() && mEnableAdvert)
2824
    {
2825
        uint8_t status = 0;
2826
        if (Net::getNetworkType() == ServerType::TMWATHENA)
2827
        {
2828
            if (mTradebot &&
2829
                shopWindow != nullptr &&
2830
                !shopWindow->isShopEmpty())
2831
            {
2832
                status |= BeingFlag::SHOP;
2833
            }
2834
        }
2835
        if (settings.awayMode || settings.pseudoAwayMode)
2836
            status |= BeingFlag::AWAY;
2837
2838
        if (mInactive)
2839
            status |= BeingFlag::INACTIVE;
2840
2841
        playerHandler->updateStatus(status);
2842
    }
2843
}
2844
2845
void LocalPlayer::setTestParticle(const std::string &fileName,
2846
                                  const bool updateHash)
2847
{
2848
    mTestParticleName = fileName;
2849
    mTestParticleTime = cur_time;
2850
    if (mTestParticle != nullptr)
2851
    {
2852
        mChildParticleEffects.removeLocally(mTestParticle);
2853
        mTestParticle = nullptr;
2854
    }
2855
    if (!fileName.empty())
2856
    {
2857
        mTestParticle = particleEngine->addEffect(fileName, 0, 0, 0);
2858
        controlCustomParticle(mTestParticle);
2859
        if (updateHash)
2860
            mTestParticleHash = UpdaterWindow::getFileHash(mTestParticleName);
2861
    }
2862
}
2863
2864
void LocalPlayer::playerDeath()
2865
{
2866
    if (mAction != BeingAction::DEAD)
2867
    {
2868
        setAction(BeingAction::DEAD, 0);
2869
        recalcSpritesOrder();
2870
    }
2871
}
2872
2873
bool LocalPlayer::canMove() const
2874
{
2875
    return !mFreezed &&
2876
        mAction != BeingAction::DEAD &&
2877
        (serverFeatures->haveMoveWhileSit() ||
2878
        mAction != BeingAction::SIT);
2879
}
2880
2881
void LocalPlayer::freezeMoving(const int timeWaitTicks)
2882
{
2883
    if (timeWaitTicks <= 0)
2884
        return;
2885
    const int nextTime = tick_time + timeWaitTicks;
2886
    if (mUnfreezeTime < nextTime)
2887
        mUnfreezeTime = nextTime;
2888
    if (mUnfreezeTime > 0)
2889
        mFreezed = true;
2890

3
}