GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/localplayer.cpp Lines: 105 1298 8.1 %
Date: 2017-11-29 Branches: 109 1627 6.7 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2017  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "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
186
LocalPlayer::LocalPlayer(const BeingId id,
114
186
                         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
372
    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

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

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

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

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

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

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

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

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

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

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

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

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

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

744
    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

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

744
    config.addListener("showownname", this);
203

744
    config.addListener("targetDeadPlayers", this);
204

744
    serverConfig.addListener("enableBuggyServers", this);
205

744
    config.addListener("syncPlayerMove", this);
206

744
    config.addListener("syncPlayerMoveDistance", this);
207

744
    config.addListener("drawPath", this);
208

744
    config.addListener("serverAttack", this);
209

744
    config.addListener("attackMoving", this);
210

744
    config.addListener("attackNext", this);
211

744
    config.addListener("showJobExp", this);
212

744
    config.addListener("enableAdvert", this);
213

744
    config.addListener("tradebot", this);
214

744
    config.addListener("targetOnlyReachable", this);
215

744
    config.addListener("showserverpos", this);
216

744
    config.addListener("visiblenames", this);
217

744
    setShowName(config.getBoolValue("showownname"));
218
186
}
219
220
2418
LocalPlayer::~LocalPlayer()
221
{
222
186
    logger->log1("LocalPlayer::~LocalPlayer");
223
224
186
    config.removeListeners(this);
225
744
    serverConfig.removeListener("enableBuggyServers", this);
226
227
186
    navigateClean();
228
186
    mCrossX = 0;
229
186
    mCrossY = 0;
230
231
186
    updateNavigateList();
232
233
186
    if (mAwayDialog != nullptr)
234
    {
235
        soundManager.volumeRestore();
236
        delete2(mAwayDialog)
237
    }
238
372
    delete2(mAwayListener);
239
372
}
240
241
void LocalPlayer::logic()
242
{
243
    BLOCK_START("LocalPlayer::logic")
244
#ifdef USE_MUMBLE
245
    if (mumbleManager)
246
        mumbleManager->setPos(mX, mY, mDirection);
247
#endif  // USE_MUMBLE
248
249
    // Actions are allowed once per second
250
    if (get_elapsed_time(mLastAction) >= 1000)
251
        mLastAction = -1;
252
253
    if (mActivityTime == 0 || mLastAction != -1)
254
        mActivityTime = cur_time;
255
256
    if (mUnfreezeTime > 0 &&
257
        mUnfreezeTime <= tick_time)
258
    {
259
        mUnfreezeTime = 0;
260
        mFreezed = false;
261
    }
262
263
    if ((mAction != BeingAction::MOVE || mNextStep) && !mNavigatePath.empty())
264
    {
265
        mNextStep = false;
266
        int dist = 5;
267
        if (!mSyncPlayerMove)
268
            dist = 20;
269
270
        if (((mNavigateX != 0) || (mNavigateY != 0)) &&
271
            ((mCrossX + dist >= mX && mCrossX <= mX + dist
272
            && mCrossY + dist >= mY && mCrossY <= mY + dist)
273
            || ((mCrossX == 0) && (mCrossY == 0))))
274
        {
275
            const Path::const_iterator i = mNavigatePath.begin();
276
            if ((*i).x == mX && (*i).y == mY)
277
                mNavigatePath.pop_front();
278
            else
279
                setDestination((*i).x, (*i).y);
280
        }
281
    }
282
283
    // Show XP messages
284
    if (!mMessages.empty())
285
    {
286
        if (mMessageTime == 0)
287
        {
288
            const MessagePair info = mMessages.front();
289
290
            if ((particleEngine != nullptr) && (gui != nullptr))
291
            {
292
                particleEngine->addTextRiseFadeOutEffect(
293
                    info.first,
294
                    mPixelX,
295
                    mPixelY - 48,
296
                    &userPalette->getColor(info.second),
297
                    gui->getInfoParticleFont(),
298
                    true);
299
            }
300
301
            mMessages.pop_front();
302
            mMessageTime = 30;
303
        }
304
        mMessageTime--;
305
    }
306
307
    if (mTarget != nullptr)
308
    {
309
        if (mTarget->getType() == ActorType::Npc)
310
        {
311
            // NPCs are always in range
312
            mTarget->setTargetType(TargetCursorType::IN_RANGE);
313
        }
314
        else
315
        {
316
            // Find whether target is in range
317
            const int rangeX = CAST_S32(
318
                abs(mTarget->mX - mX));
319
            const int rangeY = CAST_S32(
320
                abs(mTarget->mY - mY));
321
            const int attackRange = getAttackRange();
322
            const TargetCursorTypeT targetType
323
                = rangeX > attackRange || rangeY > attackRange
324
                ? TargetCursorType::NORMAL : TargetCursorType::IN_RANGE;
325
            mTarget->setTargetType(targetType);
326
327
            if (!mTarget->isAlive() && (!mTargetDeadPlayers
328
                || mTarget->getType() != ActorType::Player))
329
            {
330
                stopAttack(true);
331
            }
332
333
            if (mKeepAttacking && (mTarget != nullptr))
334
                attack(mTarget, true);
335
        }
336
    }
337
338
    Being::logic();
339
    BLOCK_END("LocalPlayer::logic")
340
}
341
342
void LocalPlayer::slowLogic()
343
{
344
    BLOCK_START("LocalPlayer::slowLogic")
345
    const time_t time = cur_time;
346
    if ((weightNotice != nullptr) && weightNoticeTime < time)
347
    {
348
        weightNotice->scheduleDelete();
349
        weightNotice = nullptr;
350
        weightNoticeTime = 0;
351
    }
352
353
    if ((serverFeatures != nullptr) &&
354
        !serverFeatures->havePlayerStatusUpdate() &&
355
        mEnableAdvert &&
356
        !mBlockAdvert &&
357
        mAdvertTime < cur_time)
358
    {
359
        uint8_t smile = BeingFlag::SPECIAL;
360
        if (mTradebot &&
361
            shopWindow != nullptr &&
362
            !shopWindow->isShopEmpty())
363
        {
364
            smile |= BeingFlag::SHOP;
365
        }
366
367
        if (settings.awayMode || settings.pseudoAwayMode)
368
            smile |= BeingFlag::AWAY;
369
370
        if (mInactive)
371
            smile |= BeingFlag::INACTIVE;
372
373
        if (emote(smile))
374
            mAdvertTime = time + 60;
375
        else
376
            mAdvertTime = time + 30;
377
    }
378
379
    if (mTestParticleTime != time && !mTestParticleName.empty())
380
    {
381
        const unsigned long hash = UpdaterWindow::getFileHash(
382
            mTestParticleName);
383
        if (hash != mTestParticleHash)
384
        {
385
            setTestParticle(mTestParticleName, false);
386
            mTestParticleHash = hash;
387
        }
388
        mTestParticleTime = time;
389
    }
390
391
    BLOCK_END("LocalPlayer::slowLogic")
392
}
393
394
void LocalPlayer::setAction(const BeingActionT &action,
395
                            const int attackId)
396
{
397
    if (action == BeingAction::DEAD)
398
    {
399
        if (!mLastHitFrom.empty() &&
400
            !serverFeatures->haveKillerId())
401
        {
402
            // TRANSLATORS: chat message after death
403
            debugMsg(strprintf(_("You were killed by %s."),
404
                mLastHitFrom.c_str()));
405
            mLastHitFrom.clear();
406
        }
407
        setTarget(nullptr);
408
    }
409
410
    Being::setAction(action,
411
        attackId);
412
#ifdef USE_MUMBLE
413
    if (mumbleManager)
414
        mumbleManager->setAction(CAST_S32(action));
415
#endif  // USE_MUMBLE
416
}
417
418
void LocalPlayer::setGroupId(const int id)
419
{
420
    Being::setGroupId(id);
421
422
    if (id > 0)
423
    {
424
        setGM(true);
425
        if (chatWindow != nullptr)
426
        {
427
            chatWindow->loadGMCommands();
428
            chatWindow->showGMTab();
429
        }
430
    }
431
    else
432
    {
433
        setGM(false);
434
    }
435
    if (statusWindow != nullptr)
436
        statusWindow->updateLevelLabel();
437
}
438
439
void LocalPlayer::nextTile(unsigned char dir A_UNUSED = 0)
440
{
441
    const Party *const party = Party::getParty(1);
442
    if (party != nullptr)
443
    {
444
        PartyMember *const pm = party->getMember(mName);
445
        if (pm != nullptr)
446
        {
447
            pm->setX(mX);
448
            pm->setY(mY);
449
        }
450
    }
451
452
    if (mPath.empty())
453
    {
454
        if (mPickUpTarget != nullptr)
455
            pickUp(mPickUpTarget);
456
457
        if (mWalkingDir != 0u)
458
            startWalking(mWalkingDir);
459
    }
460
    else if (mPath.size() == 1)
461
    {
462
        if (mPickUpTarget != nullptr)
463
            pickUp(mPickUpTarget);
464
    }
465
466
    if (mGoingToTarget && (mTarget != nullptr) && withinAttackRange(mTarget))
467
    {
468
        mAction = BeingAction::STAND;
469
        attack(mTarget, true);
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
44
Being *LocalPlayer::getTarget() const
544
{
545
44
    return mTarget;
546
}
547
548
14
void LocalPlayer::setTarget(Being *const target)
549
{
550
14
    if (target == this && (target != nullptr))
551
        return;
552
553
14
    if (target == mTarget)
554
        return;
555
556
8
    Being *oldTarget = nullptr;
557
8
    if (mTarget != nullptr)
558
    {
559
4
        mTarget->untarget();
560
4
        oldTarget = mTarget;
561
    }
562
563
8
    if (mTarget != nullptr)
564
    {
565
8
        if (mTarget->getType() == ActorType::Monster)
566
2
            mTarget->setShowName(false);
567
    }
568
569
8
    mTarget = target;
570
571
8
    if (oldTarget != nullptr)
572
4
        oldTarget->updateName();
573
574
8
    if (target != nullptr)
575
    {
576
4
        mLastTargetX = target->mX;
577
4
        mLastTargetY = target->mY;
578
4
        target->updateName();
579
4
        if (mVisibleNames == VisibleName::ShowOnSelection)
580
            target->setShowName(true);
581
    }
582

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

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


1674
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1762
1763

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

6
}