GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/localplayer.cpp Lines: 108 1314 8.2 %
Date: 2019-06-03 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
                const int offset = CAST_S32(mStatusEffectIcons.size());
1105
                if (miniStatusWindow != nullptr)
1106
                    miniStatusWindow->setIcon(offset, sprite);
1107
                mStatusEffectIcons.push_back(effectId);
1108
            }
1109
        }
1110
    }
1111
}
1112
1113
void LocalPlayer::addMessageToQueue(const std::string &message,
1114
                                    const UserColorIdT color)
1115
{
1116
    if (mMessages.size() < 20)
1117
        mMessages.push_back(MessagePair(message, color));
1118
}
1119
1120
void LocalPlayer::optionChanged(const std::string &value)
1121
{
1122
    if (value == "showownname")
1123
    {
1124
        setShowName(config.getBoolValue("showownname"));
1125
    }
1126
    else if (value == "targetDeadPlayers")
1127
    {
1128
        mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers");
1129
    }
1130
    else if (value == "enableBuggyServers")
1131
    {
1132
        mIsServerBuggy = serverConfig.getBoolValue("enableBuggyServers");
1133
    }
1134
    else if (value == "syncPlayerMove")
1135
    {
1136
        mSyncPlayerMove = config.getBoolValue("syncPlayerMove");
1137
    }
1138
    else if (value == "syncPlayerMoveDistance")
1139
    {
1140
#ifdef TMWA_SUPPORT
1141
        if (Net::getNetworkType() != ServerType::TMWATHENA)
1142
#endif
1143
        {
1144
            mSyncPlayerMoveDistance =
1145
                config.getIntValue("syncPlayerMoveDistance");
1146
        }
1147
    }
1148
#ifdef TMWA_SUPPORT
1149
    else if (value == "syncPlayerMoveDistanceLegacy")
1150
    {
1151
        if (Net::getNetworkType() == ServerType::TMWATHENA)
1152
        {
1153
            mSyncPlayerMoveDistance =
1154
                config.getIntValue("syncPlayerMoveDistanceLegacy");
1155
        }
1156
    }
1157
#endif
1158
    else if (value == "drawPath")
1159
    {
1160
        mDrawPath = config.getBoolValue("drawPath");
1161
    }
1162
    else if (value == "serverAttack")
1163
    {
1164
        mServerAttack = fromBool(config.getBoolValue("serverAttack"), Keep);
1165
    }
1166
    else if (value == "attackMoving")
1167
    {
1168
        mAttackMoving = config.getBoolValue("attackMoving");
1169
    }
1170
    else if (value == "attackNext")
1171
    {
1172
        mAttackNext = config.getBoolValue("attackNext");
1173
    }
1174
    else if (value == "showJobExp")
1175
    {
1176
        mShowJobExp = config.getBoolValue("showJobExp");
1177
    }
1178
    else if (value == "enableAdvert")
1179
    {
1180
        mEnableAdvert = config.getBoolValue("enableAdvert");
1181
    }
1182
    else if (value == "tradebot")
1183
    {
1184
        mTradebot = config.getBoolValue("tradebot");
1185
    }
1186
    else if (value == "targetOnlyReachable")
1187
    {
1188
        mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable");
1189
    }
1190
    else if (value == "showserverpos")
1191
    {
1192
        mShowServerPos = config.getBoolValue("showserverpos");
1193
    }
1194
    else if (value == "visiblenames")
1195
    {
1196
        mVisibleNames = static_cast<VisibleName::Type>(
1197
            config.getIntValue("visiblenames"));
1198
    }
1199
}
1200
1201
void LocalPlayer::addJobMessage(const int64_t change)
1202
{
1203
    if (change != 0 && mMessages.size() < 20)
1204
    {
1205
        const std::string xpStr = toString(CAST_U64(change));
1206
        if (!mMessages.empty())
1207
        {
1208
            MessagePair pair = mMessages.back();
1209
            // TRANSLATORS: this is normal experience
1210
            if (pair.first.find(strprintf(" %s", _("xp"))) ==
1211
                // TRANSLATORS: this is normal experience
1212
                pair.first.size() - strlen(_("xp")) - 1)
1213
            {
1214
                mMessages.pop_back();
1215
                pair.first.append(strprintf(", %s %s",
1216
                    xpStr.c_str(),
1217
                    // TRANSLATORS: this is job experience
1218
                    _("job")));
1219
                mMessages.push_back(pair);
1220
            }
1221
            else
1222
            {
1223
                addMessageToQueue(strprintf("%s %s",
1224
                    xpStr.c_str(),
1225
                    // TRANSLATORS: this is job experience
1226
                    _("job")),
1227
                    UserColorId::EXP_INFO);
1228
            }
1229
        }
1230
        else
1231
        {
1232
            addMessageToQueue(strprintf("%s %s",
1233
                xpStr.c_str(),
1234
                // TRANSLATORS: this is job experience
1235
                _("job")),
1236
                UserColorId::EXP_INFO);
1237
        }
1238
    }
1239
}
1240
1241
void LocalPlayer::addXpMessage(const int64_t change)
1242
{
1243
    if (change != 0 && mMessages.size() < 20)
1244
    {
1245
        addMessageToQueue(strprintf("%s %s",
1246
            toString(CAST_U64(change)).c_str(),
1247
            // TRANSLATORS: get xp message
1248
            _("xp")),
1249
            UserColorId::EXP_INFO);
1250
    }
1251
}
1252
1253
void LocalPlayer::addHomunXpMessage(const int change)
1254
{
1255
    if (change != 0 && mMessages.size() < 20)
1256
    {
1257
        addMessageToQueue(strprintf("%s %d %s",
1258
            // TRANSLATORS: get homunculus xp message
1259
            _("Homun"),
1260
            change,
1261
            // TRANSLATORS: get xp message
1262
            _("xp")),
1263
            UserColorId::EXP_INFO);
1264
    }
1265
}
1266
1267
void LocalPlayer::addHpMessage(const int change)
1268
{
1269
    if (change != 0 && mMessages.size() < 20)
1270
    {
1271
        // TRANSLATORS: get hp message
1272
        addMessageToQueue(strprintf("%d %s", change, _("hp")),
1273
            UserColorId::EXP_INFO);
1274
    }
1275
}
1276
1277
void LocalPlayer::addSpMessage(const int change)
1278
{
1279
    if (change != 0 && mMessages.size() < 20)
1280
    {
1281
        // TRANSLATORS: get hp message
1282
        addMessageToQueue(strprintf("%d %s", change, _("mana")),
1283
            UserColorId::EXP_INFO);
1284
    }
1285
}
1286
1287
void LocalPlayer::attributeChanged(const AttributesT id,
1288
                                   const int64_t oldVal,
1289
                                   const int64_t newVal)
1290
{
1291
    PRAGMA45(GCC diagnostic push)
1292
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
1293
    switch (id)
1294
    {
1295
        case Attributes::PLAYER_EXP:
1296
        {
1297
            if (Net::getNetworkType() != ServerType::TMWATHENA)
1298
                break;
1299
            if (oldVal > newVal)
1300
                break;
1301
1302
            const int change = CAST_S32(newVal - oldVal);
1303
            addXpMessage(change);
1304
            break;
1305
        }
1306
        case Attributes::PLAYER_BASE_LEVEL:
1307
            mLevel = CAST_S32(newVal);
1308
            break;
1309
        case Attributes::PLAYER_HP:
1310
            if (oldVal != 0 && newVal == 0)
1311
                PlayerDeathListener::distributeEvent();
1312
            break;
1313
        case Attributes::PLAYER_JOB_EXP:
1314
        {
1315
            if (!mShowJobExp ||
1316
                Net::getNetworkType() != ServerType::TMWATHENA)
1317
            {
1318
                return;
1319
            }
1320
            if (oldVal > newVal ||
1321
                PlayerInfo::getAttribute(
1322
                Attributes::PLAYER_JOB_EXP_NEEDED) == 0)
1323
            {
1324
                return;
1325
            }
1326
            const int32_t change = CAST_S32(newVal - oldVal);
1327
            addJobMessage(change);
1328
            break;
1329
        }
1330
        default:
1331
            break;
1332
    }
1333
    PRAGMA45(GCC diagnostic pop)
1334
}
1335
1336
void LocalPlayer::move(const int dX, const int dY)
1337
{
1338
    mPickUpTarget = nullptr;
1339
    setDestination(mX + dX, mY + dY);
1340
}
1341
1342
void LocalPlayer::moveToTarget(int dist)
1343
{
1344
    bool gotPos(false);
1345
    Path debugPath;
1346
1347
    size_t limit(0);
1348
1349
    if (dist == -1)
1350
    {
1351
        dist = settings.moveToTargetType;
1352
        if (dist != 0)
1353
        {
1354
            const bool broken = (Net::getNetworkType() ==
1355
                ServerType::TMWATHENA);
1356
            switch (dist)
1357
            {
1358
                case 10:
1359
                    dist = mAttackRange;
1360
                    if (dist == 1 && broken)
1361
                        dist = 2;
1362
                    break;
1363
                case 11:
1364
                    dist = mAttackRange - 1;
1365
                    if (dist < 1)
1366
                        dist = 1;
1367
                    if (dist == 1 && broken)
1368
                        dist = 2;
1369
                    break;
1370
                default:
1371
                    break;
1372
            }
1373
        }
1374
    }
1375
1376
    if (mTarget != nullptr)
1377
    {
1378
        if (mMap != nullptr)
1379
        {
1380
            debugPath = mMap->findPath(
1381
                (mPixelX - mapTileSize / 2) / mapTileSize,
1382
                (mPixelY - mapTileSize) / mapTileSize,
1383
                mTarget->mX,
1384
                mTarget->mY,
1385
                getBlockWalkMask(),
1386
                0);
1387
        }
1388
1389
        const size_t sz = debugPath.size();
1390
        if (sz < CAST_SIZE(dist))
1391
            return;
1392
        limit = CAST_S32(sz) - dist;
1393
        gotPos = true;
1394
    }
1395
    else if ((mNavigateX != 0) || (mNavigateY != 0))
1396
    {
1397
        debugPath = mNavigatePath;
1398
        limit = dist;
1399
        gotPos = true;
1400
    }
1401
1402
    if (gotPos)
1403
    {
1404
        if (dist == 0)
1405
        {
1406
            if (mTarget != nullptr)
1407
                navigateTo(mTarget->mX, mTarget->mY);
1408
        }
1409
        else
1410
        {
1411
            Position pos(0, 0);
1412
            size_t f = 0;
1413
1414
            for (Path::const_iterator i = debugPath.begin(),
1415
                 i_fend = debugPath.end();
1416
                 i != i_fend && f < limit; ++i, f++)
1417
            {
1418
                pos = (*i);
1419
            }
1420
            navigateTo(pos.x, pos.y);
1421
        }
1422
    }
1423
    else if ((mLastTargetX != 0) || (mLastTargetY != 0))
1424
    {
1425
        navigateTo(mLastTargetX, mLastTargetY);
1426
    }
1427
}
1428
1429
void LocalPlayer::moveToHome()
1430
{
1431
    mPickUpTarget = nullptr;
1432
    if ((mX != mCrossX || mY != mCrossY) && (mCrossX != 0) && (mCrossY != 0))
1433
    {
1434
        setDestination(mCrossX, mCrossY);
1435
    }
1436
    else if (mMap != nullptr)
1437
    {
1438
        const std::map<std::string, Vector>::const_iterator iter =
1439
            mHomes.find(mMap->getProperty("_realfilename", std::string()));
1440
1441
        if (iter != mHomes.end())
1442
        {
1443
            const Vector pos = mHomes[(*iter).first];
1444
            if (mX == pos.x && mY == pos.y)
1445
            {
1446
                playerHandler->setDestination(
1447
                        CAST_S32(pos.x),
1448
                        CAST_S32(pos.y),
1449
                        CAST_S32(mDirection));
1450
            }
1451
            else
1452
            {
1453
                navigateTo(CAST_S32(pos.x), CAST_S32(pos.y));
1454
            }
1455
        }
1456
    }
1457
}
1458
1459
void LocalPlayer::changeEquipmentBeforeAttack(const Being *const target) const
1460
{
1461
    if (settings.attackWeaponType == 1
1462
        || (target == nullptr)
1463
        || (PlayerInfo::getInventory() == nullptr))
1464
    {
1465
        return;
1466
    }
1467
1468
    bool allowSword = false;
1469
    const int dx = target->mX - mX;
1470
    const int dy = target->mY - mY;
1471
    const Item *item = nullptr;
1472
1473
    if (dx * dx + dy * dy > 80)
1474
        return;
1475
1476
    if (dx * dx + dy * dy < 8)
1477
        allowSword = true;
1478
1479
    const Inventory *const inv = PlayerInfo::getInventory();
1480
    if (inv == nullptr)
1481
        return;
1482
1483
    // if attack distance for sword
1484
    if (allowSword)
1485
    {
1486
        // searching swords
1487
        const WeaponsInfos &swords = WeaponsDB::getSwords();
1488
        FOR_EACH (WeaponsInfosIter, it, swords)
1489
        {
1490
            item = inv->findItem(*it, ItemColor_zero);
1491
            if (item != nullptr)
1492
                break;
1493
        }
1494
1495
        // no swords
1496
        if (item == nullptr)
1497
            return;
1498
1499
        // if sword not equiped
1500
        if (item->isEquipped() == Equipped_false)
1501
            PlayerInfo::equipItem(item, Sfx_true);
1502
1503
        // if need equip shield too
1504
        if (settings.attackWeaponType == 3)
1505
        {
1506
            // searching shield
1507
            const WeaponsInfos &shields = WeaponsDB::getShields();
1508
            FOR_EACH (WeaponsInfosIter, it, shields)
1509
            {
1510
                item = inv->findItem(*it, ItemColor_zero);
1511
                if (item != nullptr)
1512
                    break;
1513
            }
1514
            if ((item != nullptr) && item->isEquipped() == Equipped_false)
1515
                PlayerInfo::equipItem(item, Sfx_true);
1516
        }
1517
    }
1518
    // big distance. allowed only bow
1519
    else
1520
    {
1521
        // searching bow
1522
        const WeaponsInfos &bows = WeaponsDB::getBows();
1523
        FOR_EACH (WeaponsInfosIter, it, bows)
1524
        {
1525
            item = inv->findItem(*it, ItemColor_zero);
1526
            if (item != nullptr)
1527
                break;
1528
        }
1529
1530
        // no bow
1531
        if (item == nullptr)
1532
            return;
1533
1534
        if (item->isEquipped() == Equipped_false)
1535
            PlayerInfo::equipItem(item, Sfx_true);
1536
    }
1537
}
1538
1539
bool LocalPlayer::isReachable(Being *const being,
1540
                              const int maxCost)
1541
{
1542
    if ((being == nullptr) || (mMap == nullptr))
1543
        return false;
1544
1545
    if (being->getReachable() == Reachable::REACH_NO)
1546
        return false;
1547
1548
    if (being->mX == mX &&
1549
        being->mY == mY)
1550
    {
1551
        being->setDistance(0);
1552
        being->setReachable(Reachable::REACH_YES);
1553
        return true;
1554
    }
1555
    else if (being->mX - 1 <= mX &&
1556
             being->mX + 1 >= mX &&
1557
             being->mY - 1 <= mY &&
1558
             being->mY + 1 >= mY)
1559
    {
1560
        being->setDistance(1);
1561
        being->setReachable(Reachable::REACH_YES);
1562
        return true;
1563
    }
1564
1565
    const Path debugPath = mMap->findPath(
1566
        (mPixelX - mapTileSize / 2) / mapTileSize,
1567
        (mPixelY - mapTileSize) / mapTileSize,
1568
        being->mX,
1569
        being->mY,
1570
        getBlockWalkMask(),
1571
        maxCost);
1572
1573
    being->setDistance(CAST_S32(debugPath.size()));
1574
    if (!debugPath.empty())
1575
    {
1576
        being->setReachable(Reachable::REACH_YES);
1577
        return true;
1578
    }
1579
    being->setReachable(Reachable::REACH_NO);
1580
    return false;
1581
}
1582
1583
bool LocalPlayer::isReachable(const int x, const int y,
1584
                              const bool allowCollision) const
1585
{
1586
    const WalkLayer *const walk = mMap->getWalkLayer();
1587
    if (walk == nullptr)
1588
        return false;
1589
    int num = walk->getDataAt(x, y);
1590
    if (allowCollision && num < 0)
1591
        num = -num;
1592
1593
    return walk->getDataAt(mX, mY) == num;
1594
}
1595
1596
bool LocalPlayer::pickUpItems(int pickUpType)
1597
{
1598
    if (actorManager == nullptr)
1599
        return false;
1600
1601
    bool status = false;
1602
    int x = mX;
1603
    int y = mY;
1604
1605
    // first pick up item on player position
1606
    FloorItem *item =
1607
        actorManager->findItem(x, y);
1608
    if (item != nullptr)
1609
        status = pickUp(item);
1610
1611
    if (pickUpType == 0)
1612
        pickUpType = settings.pickUpType;
1613
1614
    if (pickUpType == 0)
1615
        return status;
1616
1617
    int x1;
1618
    int y1;
1619
    int x2;
1620
    int y2;
1621
    switch (pickUpType)
1622
    {
1623
        case 1:
1624
            switch (mDirection)
1625
            {
1626
                case BeingDirection::UP   : --y; break;
1627
                case BeingDirection::DOWN : ++y; break;
1628
                case BeingDirection::LEFT : --x; break;
1629
                case BeingDirection::RIGHT: ++x; break;
1630
                default: break;
1631
            }
1632
            item = actorManager->findItem(x, y);
1633
            if (item != nullptr)
1634
                status = pickUp(item);
1635
            break;
1636
        case 2:
1637
            switch (mDirection)
1638
            {
1639
                case BeingDirection::UP:
1640
                    x1 = x - 1; y1 = y - 1; x2 = x + 1; y2 = y; break;
1641
                case BeingDirection::DOWN:
1642
                    x1 = x - 1; y1 = y; x2 = x + 1; y2 = y + 1; break;
1643
                case BeingDirection::LEFT:
1644
                    x1 = x - 1; y1 = y - 1; x2 = x; y2 = y + 1; break;
1645
                case BeingDirection::RIGHT:
1646
                    x1 = x; y1 = y - 1; x2 = x + 1; y2 = y + 1; break;
1647
                default:
1648
                    x1 = x; x2 = x; y1 = y; y2 = y; break;
1649
            }
1650
            if (actorManager->pickUpAll(x1, y1, x2, y2, false))
1651
                status = true;
1652
            break;
1653
        case 3:
1654
            if (actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1655
                status = true;
1656
            break;
1657
1658
        case 4:
1659
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1660
            {
1661
                if (actorManager->pickUpNearest(x, y, 4))
1662
                    status = true;
1663
            }
1664
            else
1665
            {
1666
                status = true;
1667
            }
1668
            break;
1669
1670
        case 5:
1671
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1672
            {
1673
                if (actorManager->pickUpNearest(x, y, 8))
1674
                    status = true;
1675
            }
1676
            else
1677
            {
1678
                status = true;
1679
            }
1680
            break;
1681
1682
        case 6:
1683
            if (!actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false))
1684
            {
1685
                if (actorManager->pickUpNearest(x, y, 90))
1686
                    status = true;
1687
            }
1688
            else
1689
            {
1690
                status = true;
1691
            }
1692
            break;
1693
1694
        default:
1695
            break;
1696
    }
1697
    return status;
1698
}
1699
1700
1701
void LocalPlayer::moveByDirection(const unsigned char dir)
1702
{
1703
    int dx = 0;
1704
    int dy = 0;
1705
    if ((dir & BeingDirection::UP) != 0)
1706
        dy--;
1707
    if ((dir & BeingDirection::DOWN) != 0)
1708
        dy++;
1709
    if ((dir & BeingDirection::LEFT) != 0)
1710
        dx--;
1711
    if ((dir & BeingDirection::RIGHT) != 0)
1712
        dx++;
1713
    move(dx, dy);
1714
}
1715
1716
void LocalPlayer::specialMove(const unsigned char direction)
1717
{
1718
    if ((direction != 0U) && ((mNavigateX != 0) || (mNavigateY != 0)))
1719
        navigateClean();
1720
1721
    if ((direction != 0U) && (settings.moveType >= 2
1722
        && settings.moveType <= 4))
1723
    {
1724
        if (mAction == BeingAction::MOVE)
1725
            return;
1726
1727
        unsigned int max;
1728
1729
        if (settings.moveType == 2)
1730
            max = 5;
1731
        else if (settings.moveType == 4)
1732
            max = 1;
1733
        else
1734
            max = 3;
1735
1736
        if (getMoveState() < max)
1737
        {
1738
            moveByDirection(direction);
1739
            mMoveState ++;
1740
        }
1741
        else
1742
        {
1743
            mMoveState = 0;
1744
            crazyMoves->crazyMove();
1745
        }
1746
    }
1747
    else
1748
    {
1749
        setWalkingDir(direction);
1750
    }
1751
}
1752
1753
#ifdef TMWA_SUPPORT
1754
void LocalPlayer::magicAttack() const
1755
{
1756
    if (Net::getNetworkType() != ServerType::TMWATHENA)
1757
        return;
1758
    if (chatWindow == nullptr ||
1759
        !isAlive() ||
1760
        !playerHandler->canUseMagic())
1761
    {
1762
        return;
1763
    }
1764
1765
    switch (settings.magicAttackType)
1766
    {
1767
        // flar W00
1768
        case 0:
1769
            tryMagic("#flar", 1, 0, 10);
1770
            break;
1771
        // chiza W01
1772
        case 1:
1773
            tryMagic("#chiza", 1, 0,  9);
1774
            break;
1775
        // ingrav W10
1776
        case 2:
1777
            tryMagic("#ingrav", 2, 2,  20);
1778
            break;
1779
        // frillyar W11
1780
        case 3:
1781
            tryMagic("#frillyar", 2, 2, 25);
1782
            break;
1783
        // upmarmu W12
1784
        case 4:
1785
            tryMagic("#upmarmu", 2, 2, 20);
1786
            break;
1787
        default:
1788
            break;
1789
    }
1790
}
1791
1792
void LocalPlayer::tryMagic(const std::string &spell, const int baseMagic,
1793
                           const int schoolMagic, const int mana)
1794
{
1795
    if (chatWindow == nullptr)
1796
        return;
1797
1798
    if (PlayerInfo::getSkillLevel(340) >= baseMagic
1799
        && PlayerInfo::getSkillLevel(342) >= schoolMagic)
1800
    {
1801
        if (PlayerInfo::getAttribute(Attributes::PLAYER_MP) >= mana)
1802
        {
1803
            if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT))
1804
                return;
1805
1806
            chatWindow->localChatInput(spell);
1807
        }
1808
    }
1809
}
1810
#endif  // TMWA_SUPPORT
1811
1812
94
void LocalPlayer::loadHomes()
1813
{
1814
188
    std::string buf;
1815


846
    std::stringstream ss(serverConfig.getValue("playerHomes", ""));
1816
1817

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

3
}