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


846
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1782
1783

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

3
}