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

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "being/localplayer.h"
25
26
#include "actormanager.h"
27
#include "configuration.h"
28
#include "gamemodifiers.h"
29
#include "guild.h"
30
#include "party.h"
31
#include "settings.h"
32
#include "soundmanager.h"
33
#include "statuseffect.h"
34
35
#include "being/beingflag.h"
36
#include "being/crazymoves.h"
37
#include "being/playerinfo.h"
38
#include "being/playerrelations.h"
39
40
#include "const/sound.h"
41
42
#include "enums/equipslot.h"
43
44
#include "enums/being/beingdirection.h"
45
46
#include "enums/resources/map/blockmask.h"
47
#include "enums/resources/map/mapitemtype.h"
48
49
#include "particle/particleengine.h"
50
51
#include "input/keyboardconfig.h"
52
53
#include "gui/gui.h"
54
#include "gui/userpalette.h"
55
#include "gui/popupmanager.h"
56
57
#include "gui/windows/chatwindow.h"
58
#include "gui/windows/ministatuswindow.h"
59
#include "gui/windows/okdialog.h"
60
#include "gui/windows/outfitwindow.h"
61
#include "gui/windows/shopwindow.h"
62
#include "gui/windows/socialwindow.h"
63
#include "gui/windows/statuswindow.h"
64
#include "gui/windows/updaterwindow.h"
65
66
#include "gui/widgets/tabs/chat/whispertab.h"
67
68
#include "listeners/awaylistener.h"
69
70
#include "net/beinghandler.h"
71
#include "net/chathandler.h"
72
#include "net/inventoryhandler.h"
73
#include "net/net.h"
74
#include "net/packetlimiter.h"
75
#include "net/playerhandler.h"
76
#include "net/serverfeatures.h"
77
78
#include "resources/iteminfo.h"
79
80
#include "resources/db/weaponsdb.h"
81
82
#include "resources/item/item.h"
83
84
#include "resources/map/map.h"
85
#include "resources/map/mapitem.h"
86
#include "resources/map/speciallayer.h"
87
#include "resources/map/walklayer.h"
88
89
#include "resources/sprite/animatedsprite.h"
90
91
#include "utils/delete2.h"
92
#include "utils/foreach.h"
93
#include "utils/gettext.h"
94
#include "utils/timer.h"
95
96
#ifdef USE_MUMBLE
97
#include "mumblemanager.h"
98
#endif  // USE_MUMBLE
99
100
#include <climits>
101
102
#include "debug.h"
103
104
static const int16_t awayLimitTimer = 60;
105
static const int MAX_TICK_VALUE = INT_MAX / 2;
106
107
typedef std::map<int, Guild*>::const_iterator GuildMapCIter;
108
109
LocalPlayer *localPlayer = nullptr;
110
111
extern OkDialog *weightNotice;
112
extern time_t weightNoticeTime;
113
114
94
LocalPlayer::LocalPlayer(const BeingId id,
115
94
                         const BeingTypeId subType) :
116
    Being(id, ActorType::Player),
117
    ActorSpriteListener(),
118
    AttributeListener(),
119
    PlayerDeathListener(),
120
    mMoveState(0),
121
    mLastTargetX(0),
122
    mLastTargetY(0),
123
    mHomes(),
124
    mTarget(nullptr),
125
    mPlayerFollowed(),
126
    mPlayerImitated(),
127
    mNextDestX(0),
128
    mNextDestY(0),
129
    mPickUpTarget(nullptr),
130
    mLastAction(-1),
131
    mStatusEffectIcons(),
132
    mMessages(),
133
    mMessageTime(0),
134
188
    mAwayListener(new AwayListener),
135
    mAwayDialog(nullptr),
136
    mPingSendTick(0),
137
    mPingTime(0),
138
    mAfkTime(0),
139
    mActivityTime(0),
140
    mNavigateX(0),
141
    mNavigateY(0),
142
    mNavigateId(BeingId_zero),
143
    mCrossX(0),
144
    mCrossY(0),
145
    mOldX(0),
146
    mOldY(0),
147
    mOldTileX(0),
148
    mOldTileY(0),
149
    mNavigatePath(),
150
    mLastHitFrom(),
151
    mWaitFor(),
152
    mAdvertTime(0),
153
    mTestParticle(nullptr),
154
    mTestParticleName(),
155
    mTestParticleTime(0),
156
    mTestParticleHash(0L),
157

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2632
    mFreezed(false)
184
{
185
94
    logger->log1("LocalPlayer::LocalPlayer");
186
187
#ifdef TMWA_SUPPORT
188

94
    if (Net::getNetworkType() == ServerType::TMWATHENA)
189
    {
190
        mSyncPlayerMoveDistance =
191
            config.getIntValue("syncPlayerMoveDistanceLegacy");
192
    }
193
#endif
194
195
94
    postInit(subType, nullptr);
196
94
    mAttackRange = 0;
197
94
    mLevel = 1;
198
94
    mAdvanced = true;
199
94
    mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
200
94
    if (userPalette != nullptr)
201
69
        mNameColor = &userPalette->getColor(UserColorId::SELF, 255U);
202
    else
203
25
        mNameColor = nullptr;
204
205
188
    PlayerInfo::setStatBase(Attributes::PLAYER_WALK_SPEED,
206
        getWalkSpeed(),
207
94
        Notify_true);
208
    PlayerInfo::setStatMod(Attributes::PLAYER_WALK_SPEED,
209
        0,
210
94
        Notify_true);
211
212
94
    loadHomes();
213
214

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

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

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

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

376
    config.addListener("syncPlayerMoveDistance", this);
219
#ifdef TMWA_SUPPORT
220

376
    config.addListener("syncPlayerMoveDistanceLegacy", this);
221
#endif
222

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

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

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

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

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

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

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

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

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

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

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

7
    if (target == this && (target != nullptr))
570
        return;
571
572
7
    if (target == mTarget)
573
        return;
574
575
4
    Being *oldTarget = nullptr;
576
4
    if (mTarget != nullptr)
577
    {
578
2
        mTarget->untarget();
579
2
        oldTarget = mTarget;
580
    }
581
582
4
    if (mTarget != nullptr)
583
    {
584
4
        if (mTarget->getType() == ActorType::Monster)
585
1
            mTarget->setShowName(false);
586
    }
587
588
4
    mTarget = target;
589
590
4
    if (oldTarget != nullptr)
591
2
        oldTarget->updateName();
592
593
4
    if (target != nullptr)
594
    {
595
2
        mLastTargetX = target->mX;
596
2
        mLastTargetY = target->mY;
597
2
        target->updateName();
598
2
        if (mVisibleNames == VisibleName::ShowOnSelection)
599
            target->setShowName(true);
600
    }
601

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

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


846
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1819
1820

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

3
}