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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

3
}