GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/localplayer.cpp Lines: 106 1290 8.2 %
Date: 2018-07-14 Branches: 111 1699 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-2018  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "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
            setAction(BeingAction::STAND, 0);
484
        else
485
            mNextStep = true;
486
    }
487
    else
488
    {
489
        Being::nextTile();
490
    }
491
}
492
493
bool LocalPlayer::pickUp(FloorItem *const item)
494
{
495
    if (item == nullptr)
496
        return false;
497
498
    if (!PacketLimiter::limitPackets(PacketType::PACKET_PICKUP))
499
        return false;
500
501
    const int dx = item->getTileX() - mX;
502
    const int dy = item->getTileY() - mY;
503
    int dist = 6;
504
505
    const unsigned int pickUpType = settings.pickUpType;
506
    if (pickUpType >= 4 && pickUpType <= 6)
507
        dist = 4;
508
509
    if (dx * dx + dy * dy < dist)
510
    {
511
        if ((actorManager != nullptr) && actorManager->checkForPickup(item))
512
        {
513
            PlayerInfo::pickUpItem(item, Sfx_true);
514
            mPickUpTarget = nullptr;
515
        }
516
    }
517
    else if (pickUpType >= 4 && pickUpType <= 6)
518
    {
519
        const Path debugPath = mMap->findPath(
520
            (mPixelX - mapTileSize / 2) / mapTileSize,
521
            (mPixelY - mapTileSize) / mapTileSize,
522
            item->getTileX(),
523
            item->getTileY(),
524
            getBlockWalkMask(),
525
            0);
526
        if (!debugPath.empty())
527
            navigateTo(item->getTileX(), item->getTileY());
528
        else
529
            setDestination(item->getTileX(), item->getTileY());
530
531
        mPickUpTarget = item;
532
        mPickUpTarget->addActorSpriteListener(this);
533
    }
534
    return true;
535
}
536
537
void LocalPlayer::actorSpriteDestroyed(const ActorSprite &actorSprite)
538
{
539
    if (mPickUpTarget == &actorSprite)
540
        mPickUpTarget = nullptr;
541
}
542
543
22
Being *LocalPlayer::getTarget() const
544
{
545
22
    return mTarget;
546
}
547
548
7
void LocalPlayer::setTarget(Being *const target)
549
{
550

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

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

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


846
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1777
1778

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

3
}