GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/being.cpp Lines: 204 2658 7.7 %
Date: 2018-05-19 03:07:18 Branches: 137 2785 4.9 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2018  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "being/being.h"
24
25
#include "actormanager.h"
26
#include "beingequipbackend.h"
27
#include "configuration.h"
28
#include "effectmanager.h"
29
#include "guild.h"
30
#include "itemcolormanager.h"
31
#include "party.h"
32
#include "settings.h"
33
#include "soundmanager.h"
34
#include "text.h"
35
36
#include "being/beingcacheentry.h"
37
#include "being/beingflag.h"
38
#include "being/beingspeech.h"
39
#include "being/castingeffect.h"
40
#include "being/localplayer.h"
41
#include "being/playerinfo.h"
42
#include "being/playerrelations.h"
43
#include "being/homunculusinfo.h"
44
#include "being/mercenaryinfo.h"
45
46
#include "const/utils/timer.h"
47
48
#include "const/resources/spriteaction.h"
49
50
#include "enums/being/beingdirection.h"
51
52
#include "enums/resources/map/blockmask.h"
53
54
#include "fs/files.h"
55
56
#include "gui/gui.h"
57
#include "gui/userpalette.h"
58
59
#include "gui/fonts/font.h"
60
61
#include "gui/popups/speechbubble.h"
62
63
#include "gui/windows/chatwindow.h"
64
#include "gui/windows/equipmentwindow.h"
65
#include "gui/windows/skilldialog.h"
66
#include "gui/windows/socialwindow.h"
67
68
#include "net/charserverhandler.h"
69
#include "net/gamehandler.h"
70
#include "net/homunculushandler.h"
71
#include "net/mercenaryhandler.h"
72
#include "net/net.h"
73
#include "net/npchandler.h"
74
#include "net/packetlimiter.h"
75
#include "net/playerhandler.h"
76
#include "net/serverfeatures.h"
77
78
#include "particle/particleinfo.h"
79
80
#include "resources/attack.h"
81
#include "resources/chatobject.h"
82
#include "resources/emoteinfo.h"
83
#include "resources/emotesprite.h"
84
#include "resources/horseinfo.h"
85
#include "resources/iteminfo.h"
86
87
#include "resources/db/avatardb.h"
88
#include "resources/db/badgesdb.h"
89
#include "resources/db/groupdb.h"
90
#include "resources/db/elementaldb.h"
91
#include "resources/db/emotedb.h"
92
#include "resources/db/homunculusdb.h"
93
#include "resources/db/horsedb.h"
94
#include "resources/db/languagedb.h"
95
#include "resources/db/mercenarydb.h"
96
#include "resources/db/monsterdb.h"
97
#include "resources/db/npcdb.h"
98
#include "resources/db/petdb.h"
99
#include "resources/db/skillunitdb.h"
100
101
#include "resources/image/image.h"
102
103
#include "resources/item/item.h"
104
105
#include "resources/map/map.h"
106
107
#include "resources/skill/skilldata.h"
108
#include "resources/skill/skillinfo.h"
109
110
#include "resources/sprite/animatedsprite.h"
111
112
#include "gui/widgets/createwidget.h"
113
114
#include "utils/checkutils.h"
115
#include "utils/delete2.h"
116
#include "utils/foreach.h"
117
#include "utils/gettext.h"
118
#include "utils/likely.h"
119
#include "utils/stdmove.h"
120
#include "utils/timer.h"
121
122
#include "debug.h"
123
124
const unsigned int CACHE_SIZE = 50;
125
126
time_t Being::mUpdateConfigTime = 0;
127
unsigned int Being::mConfLineLim = 0;
128
int Being::mSpeechType = 0;
129
bool Being::mHighlightMapPortals = false;
130
bool Being::mHighlightMonsterAttackRange = false;
131
bool Being::mLowTraffic = true;
132
bool Being::mDrawHotKeys = true;
133
bool Being::mShowBattleEvents = false;
134
bool Being::mShowMobHP = false;
135
bool Being::mShowOwnHP = false;
136
bool Being::mShowGender = false;
137
bool Being::mShowLevel = false;
138
bool Being::mShowPlayersStatus = false;
139
bool Being::mEnableReorderSprites = true;
140
bool Being::mHideErased = false;
141
Move Being::mMoveNames = Move_false;
142
bool Being::mUseDiagonal = true;
143
BadgeDrawType::Type Being::mShowBadges = BadgeDrawType::Top;
144
int Being::mAwayEffect = -1;
145
VisibleNamePos::Type Being::mVisibleNamePos = VisibleNamePos::Bottom;
146
147
2
std::list<BeingCacheEntry*> beingInfoCache;
148
typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
149
typedef std::map<int, int>::const_iterator IntMapCIter;
150
151
static const unsigned int SPEECH_TIME = 500;
152
static const unsigned int SPEECH_MIN_TIME = 200;
153
static const unsigned int SPEECH_MAX_TIME = 800;
154
155
#define for_each_badges() \
156
    for (int f = 0; f < BadgeIndex::BadgeIndexSize; f++)
157
158
#define for_each_horses(name) \
159
    FOR_EACH (STD_VECTOR<AnimatedSprite*>::const_iterator, it, name)
160
161
206
Being::Being(const BeingId id,
162
206
             const ActorTypeT type) :
163
    ActorSprite(id),
164
    mNextSound(),
165
    mInfo(BeingInfo::unknown),
166
    mEmotionSprite(nullptr),
167
    mAnimationEffect(nullptr),
168
    mCastingEffect(nullptr),
169
    mBadges(),
170
    mSpriteAction(SpriteAction::STAND),
171
    mName(),
172
    mExtName(),
173
    mRaceName(),
174
    mPartyName(),
175
    mGuildName(),
176
    mClanName(),
177
    mSpeech(),
178
    mDispName(nullptr),
179
    mNameColor(nullptr),
180
    mEquippedWeapon(nullptr),
181
    mPath(),
182
    mText(nullptr),
183
    mTextColor(nullptr),
184
    mDest(),
185
    mSlots(),
186
    mSpriteParticles(),
187
    mGuilds(),
188
    mParty(nullptr),
189
    mActionTime(0),
190
    mEmotionTime(0),
191
    mSpeechTime(0),
192
    mAttackSpeed(350),
193
    mLevel(0),
194
    mGroupId(0),
195
    mAttackRange(1),
196
    mLastAttackX(0),
197
    mLastAttackY(0),
198
    mPreStandTime(0),
199
    mGender(Gender::UNSPECIFIED),
200
    mAction(BeingAction::STAND),
201
    mSubType(fromInt(0xFFFF, BeingTypeId)),
202
    mDirection(BeingDirection::DOWN),
203
    mDirectionDelayed(0),
204
    mSpriteDirection(SpriteDirection::DOWN),
205
    mShowName(false),
206
    mIsGM(false),
207
    mType(type),
208
    mSpeechBubble(nullptr),
209

210
    mWalkSpeed(playerHandler != nullptr ?
210
4
               playerHandler->getDefaultWalkSpeed() : 1),
211

210
    mSpeed(playerHandler != nullptr ?
212
4
           playerHandler->getDefaultWalkSpeed() : 1),
213
    mIp(),
214
206
    mSpriteRemap(new int[20]),
215
206
    mSpriteHide(new int[20]),
216
206
    mSpriteDraw(new int[20]),
217
    mComment(),
218
    mBuyBoard(),
219
    mSellBoard(),
220
    mOwner(nullptr),
221
    mSpecialParticle(nullptr),
222
    mChat(nullptr),
223
    mHorseInfo(nullptr),
224
    mDownHorseSprites(),
225
    mUpHorseSprites(),
226
    mSpiritParticles(),
227
    mX(0),
228
    mY(0),
229
    mCachedX(0),
230
    mCachedY(0),
231
    mSortOffsetY(0),
232
    mPixelOffsetY(0),
233
    mFixedOffsetY(0),
234
    mOldHeight(0),
235
    mDamageTaken(0),
236
    mHP(0),
237
    mMaxHP(0),
238
    mDistance(0),
239
    mReachable(Reachable::REACH_UNKNOWN),
240
    mGoodStatus(-1),
241
    mMoveTime(0),
242
    mAttackTime(0),
243
    mTalkTime(0),
244
    mOtherTime(0),
245
    mTestTime(cur_time),
246
    mAttackDelay(0),
247
    mMinHit(0),
248
    mMaxHit(0),
249
    mCriticalHit(0),
250
    mPvpRank(0),
251
    mNumber(100),
252
    mSpiritBalls(0U),
253
    mUsageCounter(1),
254
    mKarma(0),
255
    mManner(0),
256
    mAreaSize(11),
257
    mCastEndTime(0),
258
    mLanguageId(-1),
259
    mBadgesX(0),
260
    mBadgesY(0),
261
    mCreatorId(BeingId_zero),
262
    mTeamId(0U),
263
    mLook(0U),
264
    mBadgesCount(0U),
265
    mHairColor(ItemColor_zero),
266
    mErased(false),
267
    mEnemy(false),
268
    mGotComment(false),
269
    mAdvanced(false),
270
    mShop(false),
271
    mAway(false),
272
    mInactive(false),
273
    mNeedPosUpdate(true),
274
    mBotAi(true),
275
5768
    mAllowNpcEquipment(false)
276
{
277
4326
    for (int f = 0; f < 20; f ++)
278
    {
279
4120
        mSpriteRemap[f] = f;
280
4120
        mSpriteHide[f] = 0;
281
4120
        mSpriteDraw[f] = 0;
282
    }
283
284
4326
    for_each_badges()
285
2060
        mBadges[f] = nullptr;
286
206
}
287
288
206
void Being::postInit(const BeingTypeId subtype,
289
                     Map *const map)
290
{
291
206
    setMap(map);
292
206
    setSubtype(subtype, 0);
293
294
206
    VisibleName::Type showName1 = VisibleName::Hide;
295
296

206
    switch (mType)
297
    {
298
        case ActorType::Player:
299
        case ActorType::Mercenary:
300
        case ActorType::Pet:
301
        case ActorType::Homunculus:
302
        case ActorType::Elemental:
303
202
            showName1 = static_cast<VisibleName::Type>(
304

808
                config.getIntValue("visiblenames"));
305
202
            break;
306
        case ActorType::Portal:
307
        case ActorType::SkillUnit:
308
            showName1 = VisibleName::Hide;
309
            break;
310
        default:
311
        case ActorType::Unknown:
312
        case ActorType::Npc:
313
        case ActorType::Monster:
314
        case ActorType::FloorItem:
315
        case ActorType::Avatar:
316
            break;
317
    }
318
319
206
    if (mType != ActorType::Npc)
320
206
        mGotComment = true;
321
322

824
    config.addListener("visiblenames", this);
323
324
206
    reReadConfig();
325
326

206
    if (mType == ActorType::Npc ||
327
        showName1 == VisibleName::Show)
328
    {
329
202
        setShowName(true);
330
    }
331
332
206
    updateColors();
333
206
    updatePercentHP();
334
206
}
335
336
4344
Being::~Being()
337
{
338
824
    config.removeListener("visiblenames", this);
339
    CHECKLISTENERS
340
341
206
    delete [] mSpriteRemap;
342
206
    mSpriteRemap = nullptr;
343
206
    delete [] mSpriteHide;
344
206
    mSpriteHide = nullptr;
345
206
    delete [] mSpriteDraw;
346
206
    mSpriteDraw = nullptr;
347
348
2266
    for_each_badges()
349
2060
        delete2(mBadges[f]);
350
351
412
    delete2(mSpeechBubble);
352
412
    delete2(mDispName);
353
206
    delete2(mText);
354
206
    delete2(mEmotionSprite);
355
206
    delete2(mAnimationEffect);
356
206
    delete2(mCastingEffect);
357
206
    mBadgesCount = 0;
358
206
    delete2(mChat);
359
206
    removeHorse();
360
361
206
    removeAllItemsParticles();
362
412
    mSpiritParticles.clear();
363
224
}
364
365
void Being::createSpeechBubble() restrict2
366
{
367
    CREATEWIDGETV0(mSpeechBubble, SpeechBubble);
368
}
369
370
206
void Being::setSubtype(const BeingTypeId subtype,
371
                       const uint16_t look) restrict2
372
{
373
206
    if (mInfo == nullptr)
374
        return;
375
376
    if (subtype == mSubType && mLook == look)
377
        return;
378
379
    mSubType = subtype;
380
    mLook = look;
381
382
    switch (mType)
383
    {
384
        case ActorType::Monster:
385
            mInfo = MonsterDB::get(mSubType);
386
            if (mInfo != nullptr)
387
            {
388
                setName(mInfo->getName());
389
                setupSpriteDisplay(mInfo->getDisplay(),
390
                    ForceDisplay_true,
391
                    DisplayType::Item,
392
                    mInfo->getColor(fromInt(mLook, ItemColor)));
393
                mYDiff = mInfo->getSortOffsetY();
394
            }
395
            break;
396
        case ActorType::Pet:
397
            mInfo = PETDB::get(mSubType);
398
            if (mInfo != nullptr)
399
            {
400
                setName(mInfo->getName());
401
                setupSpriteDisplay(mInfo->getDisplay(),
402
                    ForceDisplay_true,
403
                    DisplayType::Item,
404
                    mInfo->getColor(fromInt(mLook, ItemColor)));
405
                mYDiff = mInfo->getSortOffsetY();
406
            }
407
            break;
408
        case ActorType::Mercenary:
409
            mInfo = MercenaryDB::get(mSubType);
410
            if (mInfo != nullptr)
411
            {
412
                setName(mInfo->getName());
413
                setupSpriteDisplay(mInfo->getDisplay(),
414
                    ForceDisplay_true,
415
                    DisplayType::Item,
416
                    mInfo->getColor(fromInt(mLook, ItemColor)));
417
                mYDiff = mInfo->getSortOffsetY();
418
            }
419
            break;
420
        case ActorType::Homunculus:
421
            mInfo = HomunculusDB::get(mSubType);
422
            if (mInfo != nullptr)
423
            {
424
                setName(mInfo->getName());
425
                setupSpriteDisplay(mInfo->getDisplay(),
426
                    ForceDisplay_true,
427
                    DisplayType::Item,
428
                    mInfo->getColor(fromInt(mLook, ItemColor)));
429
                mYDiff = mInfo->getSortOffsetY();
430
            }
431
            break;
432
        case ActorType::SkillUnit:
433
            mInfo = SkillUnitDb::get(mSubType);
434
            if (mInfo != nullptr)
435
            {
436
                setName(mInfo->getName());
437
                setupSpriteDisplay(mInfo->getDisplay(),
438
                    ForceDisplay_false,
439
                    DisplayType::Item,
440
                    mInfo->getColor(fromInt(mLook, ItemColor)));
441
                mYDiff = mInfo->getSortOffsetY();
442
            }
443
            break;
444
        case ActorType::Elemental:
445
            mInfo = ElementalDb::get(mSubType);
446
            if (mInfo != nullptr)
447
            {
448
                setName(mInfo->getName());
449
                setupSpriteDisplay(mInfo->getDisplay(),
450
                    ForceDisplay_false,
451
                    DisplayType::Item,
452
                    mInfo->getColor(fromInt(mLook, ItemColor)));
453
                mYDiff = mInfo->getSortOffsetY();
454
            }
455
            break;
456
        case ActorType::Npc:
457
            mInfo = NPCDB::get(mSubType);
458
            if (mInfo != nullptr)
459
            {
460
                setupSpriteDisplay(mInfo->getDisplay(),
461
                    ForceDisplay_false,
462
                    DisplayType::Item,
463
                    std::string());
464
                mYDiff = mInfo->getSortOffsetY();
465
                mAllowNpcEquipment = mInfo->getAllowEquipment();
466
            }
467
            break;
468
        case ActorType::Avatar:
469
            mInfo = AvatarDB::get(mSubType);
470
            if (mInfo != nullptr)
471
            {
472
                setupSpriteDisplay(mInfo->getDisplay(),
473
                    ForceDisplay_false,
474
                    DisplayType::Item,
475
                    std::string());
476
            }
477
            break;
478
        case ActorType::Player:
479
        {
480
            int id = -100 - toInt(subtype, int);
481
            // Prevent showing errors when sprite doesn't exist
482
            if (!ItemDB::exists(id))
483
            {
484
                id = -100;
485
                // TRANSLATORS: default race name
486
                setRaceName(_("Human"));
487
                if (charServerHandler != nullptr)
488
                {
489
                    setSpriteId(charServerHandler->baseSprite(),
490
                        id);
491
                }
492
            }
493
            else
494
            {
495
                const ItemInfo &restrict info = ItemDB::get(id);
496
                setRaceName(info.getName());
497
                if (charServerHandler != nullptr)
498
                {
499
                    setSpriteColor(charServerHandler->baseSprite(),
500
                        id,
501
                        info.getColor(fromInt(mLook, ItemColor)));
502
                }
503
            }
504
            break;
505
        }
506
        case ActorType::Portal:
507
            break;
508
        case ActorType::Unknown:
509
        case ActorType::FloorItem:
510
        default:
511
            reportAlways("Wrong being type %d in setSubType",
512
                CAST_S32(mType));
513
            break;
514
    }
515
}
516
517
TargetCursorSizeT Being::getTargetCursorSize() const restrict2
518
{
519
    if (mInfo == nullptr)
520
        return TargetCursorSize::SMALL;
521
522
    return mInfo->getTargetCursorSize();
523
}
524
525
void Being::setPixelPositionF(const Vector &restrict pos) restrict2
526
{
527
    Actor::setPixelPositionF(pos);
528
529
    updateCoords();
530
531
    if (mText != nullptr)
532
    {
533
        mText->adviseXY(CAST_S32(pos.x),
534
            CAST_S32(pos.y) - getHeight() - mText->getHeight() - 9,
535
            mMoveNames);
536
    }
537
}
538
539
void Being::setDestination(const int dstX,
540
                           const int dstY) restrict2
541
{
542
    if (mMap == nullptr)
543
        return;
544
545
    setPath(mMap->findPath(mX,
546
        mY,
547
        dstX,
548
        dstY,
549
        getBlockWalkMask(),
550
        20));
551
}
552
553
void Being::clearPath() restrict2
554
{
555
    mPath.clear();
556
}
557
558
void Being::setPath(const Path &restrict path) restrict2
559
{
560
    mPath = path;
561
    if (mPath.empty())
562
        return;
563
564
    if (mAction != BeingAction::MOVE && mAction != BeingAction::DEAD)
565
    {
566
        nextTile();
567
        mActionTime = tick_time;
568
    }
569
}
570
571
void Being::setSpeech(const std::string &restrict text) restrict2
572
{
573
    if (userPalette == nullptr)
574
        return;
575
576
    // Remove colors
577
    mSpeech = removeColors(text);
578
579
    // Trim whitespace
580
    trim(mSpeech);
581
582
    const unsigned int lineLim = mConfLineLim;
583
    if (lineLim > 0 && mSpeech.length() > lineLim)
584
        mSpeech = mSpeech.substr(0, lineLim);
585
586
    trim(mSpeech);
587
    if (mSpeech.empty())
588
        return;
589
590
    const size_t sz = mSpeech.size();
591
    int time = 0;
592
    if (sz < 200)
593
        time = CAST_S32(SPEECH_TIME - 300 + (3 * sz));
594
595
    if (time < CAST_S32(SPEECH_MIN_TIME))
596
        time = CAST_S32(SPEECH_MIN_TIME);
597
598
    // Check for links
599
    size_t start = mSpeech.find('[');
600
    size_t e = mSpeech.find(']', start);
601
602
    while (start != std::string::npos && e != std::string::npos)
603
    {
604
        // Catch multiple embeds and ignore them so it doesn't crash the client.
605
        while ((mSpeech.find('[', start + 1) != std::string::npos) &&
606
               (mSpeech.find('[', start + 1) < e))
607
        {
608
            start = mSpeech.find('[', start + 1);
609
        }
610
611
        size_t position = mSpeech.find('|');
612
        if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
613
        {
614
            mSpeech.erase(e, 1);
615
            mSpeech.erase(start, (position - start) + 1);
616
        }
617
        position = mSpeech.find("@@");
618
619
        while (position != std::string::npos)
620
        {
621
            mSpeech.erase(position, 2);
622
            position = mSpeech.find('@');
623
        }
624
625
        start = mSpeech.find('[', start + 1);
626
        e = mSpeech.find(']', start);
627
    }
628
629
    if (!mSpeech.empty())
630
    {
631
        mSpeechTime = time <= CAST_S32(SPEECH_MAX_TIME)
632
            ? time : CAST_S32(SPEECH_MAX_TIME);
633
    }
634
635
    const int speech = mSpeechType;
636
    if (speech == BeingSpeech::TEXT_OVERHEAD)
637
    {
638
        delete mText;
639
        mText = nullptr;
640
        mText = new Text(mSpeech,
641
            mPixelX,
642
            mPixelY - getHeight(),
643
            Graphics::CENTER,
644
            &userPalette->getColor(UserColorId::PARTICLE, 255U),
645
            Speech_true,
646
            nullptr);
647
        mText->adviseXY(mPixelX,
648
            (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
649
            mMoveNames);
650
    }
651
    else
652
    {
653
        const bool isShowName = (speech == BeingSpeech::NAME_IN_BUBBLE);
654
        if (mSpeechBubble == nullptr)
655
            createSpeechBubble();
656
        if (mSpeechBubble != nullptr)
657
        {
658
            mSpeechBubble->setCaption(isShowName ? mName : "",
659
                &theme->getColor(ThemeColorId::BUBBLE_NAME, 255),
660
                &theme->getColor(ThemeColorId::BUBBLE_NAME_OUTLINE, 255));
661
            mSpeechBubble->setText(mSpeech, isShowName);
662
        }
663
    }
664
}
665
666
void Being::takeDamage(Being *restrict const attacker,
667
                       const int amount,
668
                       const AttackTypeT type,
669
                       const int attackId,
670
                       const int level) restrict2
671
{
672
    if (userPalette == nullptr || attacker == nullptr)
673
        return;
674
675
    BLOCK_START("Being::takeDamage1")
676
677
    Font *font = nullptr;
678
    const Color *color;
679
680
    if (gui != nullptr)
681
        font = gui->getInfoParticleFont();
682
683
    // Selecting the right color
684
    if (type == AttackType::CRITICAL || type == AttackType::FLEE)
685
    {
686
        if (type == AttackType::CRITICAL)
687
            attacker->setCriticalHit(amount);
688
689
        if (attacker == localPlayer)
690
        {
691
            color = &userPalette->getColor(
692
                UserColorId::HIT_LOCAL_PLAYER_CRITICAL,
693
                255U);
694
        }
695
        else
696
        {
697
            color = &userPalette->getColor(UserColorId::HIT_CRITICAL,
698
                255U);
699
        }
700
    }
701
    else if (amount == 0)
702
    {
703
        if (attacker == localPlayer)
704
        {
705
            // This is intended to be the wrong direction to visually
706
            // differentiate between hits and misses
707
            color = &userPalette->getColor(UserColorId::HIT_LOCAL_PLAYER_MISS,
708
                255U);
709
        }
710
        else
711
        {
712
            color = &userPalette->getColor(UserColorId::MISS,
713
                255U);
714
        }
715
    }
716
    else if (mType == ActorType::Monster ||
717
             mType == ActorType::Mercenary ||
718
             mType == ActorType::Pet ||
719
             mType == ActorType::Homunculus ||
720
             mType == ActorType::SkillUnit)
721
    {
722
        if (attacker == localPlayer)
723
        {
724
            color = &userPalette->getColor(
725
                UserColorId::HIT_LOCAL_PLAYER_MONSTER,
726
                255U);
727
        }
728
        else
729
        {
730
            color = &userPalette->getColor(
731
                UserColorId::HIT_PLAYER_MONSTER,
732
                255U);
733
        }
734
    }
735
    else if (mType == ActorType::Player &&
736
             attacker != localPlayer &&
737
             this == localPlayer)
738
    {
739
        // here player was attacked by other player. mark him as enemy.
740
        color = &userPalette->getColor(UserColorId::HIT_PLAYER_PLAYER,
741
            255U);
742
        attacker->setEnemy(true);
743
        attacker->updateColors();
744
    }
745
    else
746
    {
747
        color = &userPalette->getColor(UserColorId::HIT_MONSTER_PLAYER,
748
            255U);
749
    }
750
751
    if (chatWindow != nullptr && mShowBattleEvents)
752
    {
753
        if (this == localPlayer)
754
        {
755
            if (attacker->mType == ActorType::Player || (amount != 0))
756
            {
757
                ChatWindow::battleChatLog(strprintf("%s : Hit you  -%d",
758
                    attacker->getName().c_str(), amount),
759
                    ChatMsgType::BY_OTHER,
760
                    IgnoreRecord_false,
761
                    TryRemoveColors_true);
762
            }
763
        }
764
        else if (attacker == localPlayer && (amount != 0))
765
        {
766
            ChatWindow::battleChatLog(strprintf("%s : You hit %s -%d",
767
                attacker->mName.c_str(),
768
                mName.c_str(),
769
                amount),
770
                ChatMsgType::BY_PLAYER,
771
                IgnoreRecord_false,
772
                TryRemoveColors_true);
773
        }
774
    }
775
    if (font != nullptr && particleEngine != nullptr)
776
    {
777
        const std::string damage = amount != 0 ? toString(amount) :
778
            // TRANSLATORS: dodge or miss message in attacks
779
            type == AttackType::FLEE ? _("dodge") : _("miss");
780
        // Show damage number
781
        particleEngine->addTextSplashEffect(damage,
782
            mPixelX,
783
            mPixelY - 16,
784
            color,
785
            font,
786
            true);
787
    }
788
    BLOCK_END("Being::takeDamage1")
789
    BLOCK_START("Being::takeDamage2")
790
791
    if (type != AttackType::SKILL)
792
        attacker->updateHit(amount);
793
794
    if (amount > 0)
795
    {
796
        if ((localPlayer != nullptr) && localPlayer == this)
797
            localPlayer->setLastHitFrom(attacker->mName);
798
799
        mDamageTaken += amount;
800
        if (mInfo != nullptr)
801
        {
802
            playSfx(mInfo->getSound(ItemSoundEvent::HURT),
803
                this,
804
                false,
805
                mX,
806
                mY);
807
808
            if (!mInfo->isStaticMaxHP())
809
            {
810
                if ((mHP == 0) && mInfo->getMaxHP() < mDamageTaken)
811
                    mInfo->setMaxHP(mDamageTaken);
812
            }
813
        }
814
        if ((mHP != 0) && isAlive())
815
        {
816
            mHP -= amount;
817
            if (mHP < 0)
818
                mHP = 0;
819
        }
820
821
        if (mType == ActorType::Monster)
822
        {
823
            updatePercentHP();
824
            updateName();
825
        }
826
        else if (mType == ActorType::Player &&
827
                 (socialWindow != nullptr) &&
828
                 !mName.empty())
829
        {
830
            socialWindow->updateAvatar(mName);
831
        }
832
833
        if (effectManager != nullptr)
834
        {
835
            const int hitEffectId = getHitEffect(attacker,
836
                type,
837
                attackId,
838
                level);
839
            if (hitEffectId >= 0)
840
                effectManager->trigger(hitEffectId, this, 0);
841
        }
842
    }
843
    else
844
    {
845
        if (effectManager != nullptr)
846
        {
847
            int hitEffectId = -1;
848
            if (type == AttackType::SKILL)
849
            {
850
                hitEffectId = getHitEffect(attacker,
851
                    AttackType::SKILLMISS,
852
                    attackId,
853
                    level);
854
            }
855
            else
856
            {
857
                hitEffectId = getHitEffect(attacker,
858
                    AttackType::MISS,
859
                    attackId,
860
                    level);
861
            }
862
            if (hitEffectId >= 0)
863
                effectManager->trigger(hitEffectId, this, 0);
864
        }
865
    }
866
    BLOCK_END("Being::takeDamage2")
867
}
868
869
int Being::getHitEffect(const Being *restrict const attacker,
870
                        const AttackTypeT type,
871
                        const int attackId,
872
                        const int level) const restrict2
873
{
874
    if (effectManager == nullptr)
875
        return 0;
876
877
    BLOCK_START("Being::getHitEffect")
878
    // Init the particle effect path based on current
879
    // weapon or default.
880
    int hitEffectId = 0;
881
    if (type == AttackType::SKILL || type == AttackType::SKILLMISS)
882
    {
883
        const SkillData *restrict const data =
884
            skillDialog->getSkillDataByLevel(attackId, level);
885
        if (data == nullptr)
886
            return -1;
887
        if (type == AttackType::SKILL)
888
        {
889
            hitEffectId = data->hitEffectId;
890
            if (hitEffectId == -1)
891
                hitEffectId = paths.getIntValue("skillHitEffectId");
892
        }
893
        else
894
        {
895
            hitEffectId = data->missEffectId;
896
            if (hitEffectId == -1)
897
                hitEffectId = paths.getIntValue("skillMissEffectId");
898
        }
899
    }
900
    else
901
    {
902
        if (attacker != nullptr)
903
        {
904
            const ItemInfo *restrict const attackerWeapon
905
                = attacker->getEquippedWeapon();
906
            if (attackerWeapon != nullptr &&
907
                attacker->getType() == ActorType::Player)
908
            {
909
                if (type == AttackType::MISS)
910
                    hitEffectId = attackerWeapon->getMissEffectId();
911
                else if (type != AttackType::CRITICAL)
912
                    hitEffectId = attackerWeapon->getHitEffectId();
913
                else
914
                    hitEffectId = attackerWeapon->getCriticalHitEffectId();
915
            }
916
            else if (attacker->getType() == ActorType::Monster)
917
            {
918
                const BeingInfo *restrict const info = attacker->getInfo();
919
                if (info != nullptr)
920
                {
921
                    const Attack *restrict const atk =
922
                        info->getAttack(attackId);
923
                    if (atk != nullptr)
924
                    {
925
                        if (type == AttackType::MISS)
926
                            hitEffectId = atk->mMissEffectId;
927
                        else if (type != AttackType::CRITICAL)
928
                            hitEffectId = atk->mHitEffectId;
929
                        else
930
                            hitEffectId = atk->mCriticalHitEffectId;
931
                    }
932
                    else
933
                    {
934
                        hitEffectId = getDefaultEffectId(type);
935
                    }
936
                }
937
            }
938
            else
939
            {
940
                hitEffectId = getDefaultEffectId(type);
941
            }
942
        }
943
        else
944
        {
945
            hitEffectId = getDefaultEffectId(type);
946
        }
947
    }
948
    BLOCK_END("Being::getHitEffect")
949
    return hitEffectId;
950
}
951
952
int Being::getDefaultEffectId(const AttackTypeT &restrict type)
953
{
954
    if (type == AttackType::MISS)
955
        return paths.getIntValue("missEffectId");
956
    else if (type != AttackType::CRITICAL)
957
        return paths.getIntValue("hitEffectId");
958
    else
959
        return paths.getIntValue("criticalHitEffectId");
960
}
961
962
void Being::handleAttack(Being *restrict const victim,
963
                         const int damage,
964
                         const int attackId) restrict2
965
{
966
    if ((victim == nullptr) || (mInfo == nullptr))
967
        return;
968
969
    BLOCK_START("Being::handleAttack")
970
971
    if (this != localPlayer)
972
        setAction(BeingAction::ATTACK, attackId);
973
974
    mLastAttackX = victim->mX;
975
    mLastAttackY = victim->mY;
976
977
    if (mType == ActorType::Player && (mEquippedWeapon != nullptr))
978
        fireMissile(victim, mEquippedWeapon->getMissileConst());
979
    else if (mInfo->getAttack(attackId) != nullptr)
980
        fireMissile(victim, mInfo->getAttack(attackId)->mMissile);
981
982
    reset();
983
    mActionTime = tick_time;
984
985
    if (Net::getNetworkType() == ServerType::TMWATHENA &&
986
        this != localPlayer)
987
    {
988
        const uint8_t dir = calcDirection(victim->mX,
989
            victim->mY);
990
        if (dir != 0u)
991
            setDirection(dir);
992
    }
993
994
    if ((damage != 0) && victim->mType == ActorType::Player
995
        && victim->mAction == BeingAction::SIT)
996
    {
997
        victim->setAction(BeingAction::STAND, 0);
998
    }
999
1000
    if (mType == ActorType::Player)
1001
    {
1002
        if (mSlots.size() >= 10)
1003
        {
1004
            // here 10 is weapon slot
1005
            int weaponId = mSlots[10].spriteId;
1006
            if (weaponId == 0)
1007
                weaponId = -100 - toInt(mSubType, int);
1008
            const ItemInfo &info = ItemDB::get(weaponId);
1009
            playSfx(info.getSound(
1010
                (damage > 0) ? ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1011
                victim,
1012
                true,
1013
                mX, mY);
1014
        }
1015
    }
1016
    else
1017
    {
1018
        playSfx(mInfo->getSound((damage > 0) ?
1019
            ItemSoundEvent::HIT : ItemSoundEvent::MISS), victim, true, mX, mY);
1020
    }
1021
    BLOCK_END("Being::handleAttack")
1022
}
1023
1024
void Being::handleSkillCasting(Being *restrict const victim,
1025
                               const int skillId,
1026
                               const int skillLevel) restrict2
1027
{
1028
    if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1029
        return;
1030
1031
    setAction(BeingAction::CAST, skillId);
1032
1033
    const SkillData *restrict const data = skillDialog->getSkillDataByLevel(
1034
        skillId,
1035
        skillLevel);
1036
1037
    if (data != nullptr)
1038
    {
1039
        effectManager->triggerDefault(data->castingSrcEffectId,
1040
            this,
1041
            paths.getIntValue("skillCastingSrcEffectId"));
1042
        effectManager->triggerDefault(data->castingDstEffectId,
1043
            victim,
1044
            paths.getIntValue("skillCastingDstEffectId"));
1045
        fireMissile(victim, data->castingMissile);
1046
    }
1047
}
1048
1049
void Being::handleSkill(Being *restrict const victim,
1050
                        const int damage,
1051
                        const int skillId,
1052
                        const int skillLevel) restrict2
1053
{
1054
    if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1055
        return;
1056
1057
    const SkillInfo *restrict const skill = skillDialog->getSkill(skillId);
1058
    const SkillData *restrict const data = skill != nullptr
1059
        ? skill->getData1(skillLevel) : nullptr;
1060
    if (data != nullptr)
1061
    {
1062
        effectManager->triggerDefault(data->srcEffectId,
1063
            this,
1064
            paths.getIntValue("skillSrcEffectId"));
1065
        effectManager->triggerDefault(data->dstEffectId,
1066
            victim,
1067
            paths.getIntValue("skillDstEffectId"));
1068
        fireMissile(victim, data->missile);
1069
    }
1070
1071
    if (this != localPlayer && (skill != nullptr))
1072
    {
1073
        const SkillType::SkillType type = skill->type;
1074
        if ((type & SkillType::Attack) != 0 ||
1075
            (type & SkillType::Ground) != 0)
1076
        {
1077
            setAction(BeingAction::ATTACK, 1);
1078
        }
1079
        else
1080
        {
1081
            setAction(BeingAction::STAND, 1);
1082
        }
1083
    }
1084
1085
    reset();
1086
    mActionTime = tick_time;
1087
1088
    if (Net::getNetworkType() == ServerType::TMWATHENA &&
1089
        this != localPlayer)
1090
    {
1091
        const uint8_t dir = calcDirection(victim->mX,
1092
            victim->mY);
1093
        if (dir != 0u)
1094
            setDirection(dir);
1095
    }
1096
    if ((damage != 0) && victim->mType == ActorType::Player
1097
        && victim->mAction == BeingAction::SIT)
1098
    {
1099
        victim->setAction(BeingAction::STAND, 0);
1100
    }
1101
    if (data != nullptr)
1102
    {
1103
        if (damage > 0)
1104
            playSfx(data->soundHit, victim, true, mX, mY);
1105
        else
1106
            playSfx(data->soundMiss, victim, true, mX, mY);
1107
    }
1108
    else
1109
    {
1110
        playSfx(mInfo->getSound((damage > 0) ?
1111
            ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1112
            victim,
1113
            true,
1114
            mX, mY);
1115
    }
1116
}
1117
1118
12
void Being::showNameBadge(const bool show) restrict2
1119
{
1120
12
    delete2(mBadges[BadgeIndex::Name]);
1121
12
    if (show &&
1122

36
        !mName.empty() &&
1123
12
        mShowBadges != BadgeDrawType::Hide)
1124
    {
1125
24
        const std::string badge = BadgesDB::getNameBadge(mName);
1126
12
        if (!badge.empty())
1127
        {
1128
            mBadges[BadgeIndex::Name] = AnimatedSprite::load(
1129
                paths.getStringValue("badges") + badge,
1130
                0);
1131
        }
1132
    }
1133
12
}
1134
1135
14
void Being::setName(const std::string &restrict name) restrict2
1136
{
1137
28
    mExtName = name;
1138
14
    if (mType == ActorType::Npc)
1139
    {
1140
        mName = name.substr(0, name.find('#', 0));
1141
        showName();
1142
    }
1143
14
    else if (mType == ActorType::Player)
1144
    {
1145
12
        if (mName != name)
1146
        {
1147
24
            mName = name;
1148
24
            showNameBadge(!mName.empty());
1149
        }
1150
12
        if (getShowName())
1151
12
            showName();
1152
    }
1153
    else
1154
    {
1155
2
        if (mType == ActorType::Portal)
1156
            mName = name.substr(0, name.find('#', 0));
1157
        else
1158
2
            mName = name;
1159
1160
2
        if (getShowName())
1161
            showName();
1162
    }
1163
14
}
1164
1165
394
void Being::setShowName(const bool doShowName) restrict2
1166
{
1167
394
    if (mShowName == doShowName)
1168
        return;
1169
1170
206
    mShowName = doShowName;
1171
1172
206
    if (doShowName)
1173
204
        showName();
1174
    else
1175
4
        delete2(mDispName)
1176
}
1177
1178
void Being::showGuildBadge(const bool show) restrict2
1179
{
1180
    delete2(mBadges[BadgeIndex::Guild]);
1181
    if (show &&
1182
        !mGuildName.empty() &&
1183
        mShowBadges != BadgeDrawType::Hide)
1184
    {
1185
        const std::string badge = BadgesDB::getGuildBadge(mGuildName);
1186
        if (!badge.empty())
1187
        {
1188
            mBadges[BadgeIndex::Guild] = AnimatedSprite::load(
1189
                paths.getStringValue("badges") + badge,
1190
                0);
1191
        }
1192
    }
1193
}
1194
1195
void Being::setGuildName(const std::string &restrict name) restrict2
1196
{
1197
    if (mGuildName != name)
1198
    {
1199
        mGuildName = name;
1200
        showGuildBadge(!mGuildName.empty());
1201
        updateBadgesCount();
1202
    }
1203
}
1204
1205
void Being::showClanBadge(const bool show) restrict2
1206
{
1207
    delete2(mBadges[BadgeIndex::Clan]);
1208
    if (show &&
1209
        !mClanName.empty() &&
1210
        mShowBadges != BadgeDrawType::Hide)
1211
    {
1212
        const std::string badge = BadgesDB::getClanBadge(mClanName);
1213
        if (!badge.empty())
1214
        {
1215
            mBadges[BadgeIndex::Clan] = AnimatedSprite::load(
1216
                paths.getStringValue("badges") + badge,
1217
                0);
1218
        }
1219
    }
1220
}
1221
1222
void Being::setClanName(const std::string &restrict name) restrict2
1223
{
1224
    if (mClanName != name)
1225
    {
1226
        mClanName = name;
1227
        showClanBadge(!mClanName.empty());
1228
        updateBadgesCount();
1229
    }
1230
}
1231
1232
void Being::setGuildPos(const std::string &restrict pos A_UNUSED) restrict2
1233
{
1234
}
1235
1236
void Being::addGuild(Guild *restrict const guild) restrict2
1237
{
1238
    if (guild == nullptr)
1239
        return;
1240
1241
    mGuilds[guild->getId()] = guild;
1242
1243
    if (this == localPlayer && (socialWindow != nullptr))
1244
        socialWindow->addTab(guild);
1245
}
1246
1247
void Being::removeGuild(const int id) restrict2
1248
{
1249
    if (this == localPlayer && (socialWindow != nullptr))
1250
        socialWindow->removeTab(mGuilds[id]);
1251
1252
    if (mGuilds[id] != nullptr)
1253
        mGuilds[id]->removeMember(mName);
1254
    mGuilds.erase(id);
1255
}
1256
1257
const Guild *Being::getGuild(const std::string &restrict guildName) const
1258
                             restrict2
1259
{
1260
    FOR_EACH (GuildsMapCIter, itr, mGuilds)
1261
    {
1262
        const Guild *restrict const guild = itr->second;
1263
        if ((guild != nullptr) && guild->getName() == guildName)
1264
            return guild;
1265
    }
1266
1267
    return nullptr;
1268
}
1269
1270
const Guild *Being::getGuild(const int id) const restrict2
1271
{
1272
    const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
1273
    if (itr != mGuilds.end())
1274
        return itr->second;
1275
1276
    return nullptr;
1277
}
1278
1279
2
Guild *Being::getGuild() const restrict2
1280
{
1281
12
    const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
1282


12
    if (itr != mGuilds.end())
1283
        return itr->second;
1284
1285
    return nullptr;
1286
}
1287
1288
void Being::clearGuilds() restrict2
1289
{
1290
    FOR_EACH (GuildsMapCIter, itr, mGuilds)
1291
    {
1292
        Guild *const guild = itr->second;
1293
1294
        if (guild != nullptr)
1295
        {
1296
            if (this == localPlayer && (socialWindow != nullptr))
1297
                socialWindow->removeTab(guild);
1298
1299
            guild->removeMember(mId);
1300
        }
1301
    }
1302
1303
    mGuilds.clear();
1304
}
1305
1306
12
void Being::setParty(Party *restrict const party) restrict2
1307
{
1308
12
    if (party == mParty)
1309
        return;
1310
1311
12
    Party *const old = mParty;
1312
12
    mParty = party;
1313
1314
12
    if (old != nullptr)
1315
4
        old->removeMember(mId);
1316
1317
12
    if (party != nullptr)
1318
12
        party->addMember(mId, mName);
1319
1320
12
    updateColors();
1321
1322

12
    if (this == localPlayer && (socialWindow != nullptr))
1323
    {
1324
        if (old != nullptr)
1325
            socialWindow->removeTab(old);
1326
1327
        if (party != nullptr)
1328
            socialWindow->addTab(party);
1329
    }
1330
}
1331
1332
void Being::updateGuild() restrict2
1333
{
1334
    if (localPlayer == nullptr)
1335
        return;
1336
1337
    Guild *restrict const guild = localPlayer->getGuild();
1338
    if (guild == nullptr)
1339
    {
1340
        clearGuilds();
1341
        updateColors();
1342
        return;
1343
    }
1344
    if (guild->getMember(mName) != nullptr)
1345
    {
1346
        setGuild(guild);
1347
        if (!guild->getName().empty())
1348
            setGuildName(guild->getName());
1349
    }
1350
    updateColors();
1351
}
1352
1353
void Being::setGuild(Guild *restrict const guild) restrict2
1354
{
1355
    Guild *restrict const old = getGuild();
1356
    if (guild == old)
1357
        return;
1358
1359
    clearGuilds();
1360
    addGuild(guild);
1361
1362
    if (old != nullptr)
1363
        old->removeMember(mName);
1364
1365
    updateColors();
1366
1367
    if (this == localPlayer && (socialWindow != nullptr))
1368
    {
1369
        if (old != nullptr)
1370
            socialWindow->removeTab(old);
1371
1372
        if (guild != nullptr)
1373
            socialWindow->addTab(guild);
1374
    }
1375
}
1376
1377
void Being::fireMissile(Being *restrict const victim,
1378
                        const MissileInfo &restrict missile) const restrict2
1379
{
1380
    BLOCK_START("Being::fireMissile")
1381
1382
    if (victim == nullptr ||
1383
        missile.particle.empty() ||
1384
        particleEngine == nullptr)
1385
    {
1386
        BLOCK_END("Being::fireMissile")
1387
        return;
1388
    }
1389
1390
    Particle *restrict const target = particleEngine->createChild();
1391
1392
    if (target == nullptr)
1393
    {
1394
        BLOCK_END("Being::fireMissile")
1395
        return;
1396
    }
1397
1398
    // +++ add z particle position?
1399
    Particle *restrict const missileParticle = target->addEffect(
1400
        missile.particle,
1401
        mPixelX,
1402
        mPixelY,
1403
        0);
1404
1405
    target->moveBy(Vector(0.0F, 0.0F, missile.z));
1406
    target->setLifetime(missile.lifeTime);
1407
    victim->controlAutoParticle(target);
1408
1409
    if (missileParticle != nullptr)
1410
    {
1411
        missileParticle->setDestination(target, missile.speed, 0.0F);
1412
        missileParticle->setDieDistance(missile.dieDistance);
1413
        missileParticle->setLifetime(missile.lifeTime);
1414
    }
1415
    BLOCK_END("Being::fireMissile")
1416
}
1417
1418
std::string Being::getSitAction() const restrict2
1419
{
1420
    if (mHorseId != 0)
1421
        return SpriteAction::SITRIDE;
1422
    if (mMap != nullptr)
1423
    {
1424
        const unsigned char mask = mMap->getBlockMask(mX, mY);
1425
        if ((mask & BlockMask::GROUNDTOP) != 0)
1426
            return SpriteAction::SITTOP;
1427
        else if ((mask & BlockMask::AIR) != 0)
1428
            return SpriteAction::SITSKY;
1429
        else if ((mask & BlockMask::WATER) != 0)
1430
            return SpriteAction::SITWATER;
1431
    }
1432
    return SpriteAction::SIT;
1433
}
1434
1435
1436
std::string Being::getMoveAction() const restrict2
1437
{
1438
    if (mHorseId != 0)
1439
        return SpriteAction::RIDE;
1440
    if (mMap != nullptr)
1441
    {
1442
        const unsigned char mask = mMap->getBlockMask(mX, mY);
1443
        if ((mask & BlockMask::AIR) != 0)
1444
            return SpriteAction::FLY;
1445
        else if ((mask & BlockMask::WATER) != 0)
1446
            return SpriteAction::SWIM;
1447
    }
1448
    return SpriteAction::MOVE;
1449
}
1450
1451
std::string Being::getWeaponAttackAction(const ItemInfo *restrict const weapon)
1452
                                         const restrict2
1453
{
1454
    if (weapon == nullptr)
1455
        return getAttackAction();
1456
1457
    if (mHorseId != 0)
1458
        return weapon->getRideAttackAction();
1459
    if (mMap != nullptr)
1460
    {
1461
        const unsigned char mask = mMap->getBlockMask(mX, mY);
1462
        if ((mask & BlockMask::AIR) != 0)
1463
            return weapon->getSkyAttackAction();
1464
        else if ((mask & BlockMask::WATER) != 0)
1465
            return weapon->getWaterAttackAction();
1466
    }
1467
    return weapon->getAttackAction();
1468
}
1469
1470
std::string Being::getAttackAction(const Attack *restrict const attack1) const
1471
                                   restrict2
1472
{
1473
    if (attack1 == nullptr)
1474
        return getAttackAction();
1475
1476
    if (mHorseId != 0)
1477
        return attack1->mRideAction;
1478
    if (mMap != nullptr)
1479
    {
1480
        const unsigned char mask = mMap->getBlockMask(mX, mY);
1481
        if ((mask & BlockMask::AIR) != 0)
1482
            return attack1->mSkyAction;
1483
        else if ((mask & BlockMask::WATER) != 0)
1484
            return attack1->mWaterAction;
1485
    }
1486
    return attack1->mAction;
1487
}
1488
1489
std::string Being::getCastAction(const SkillInfo *restrict const skill) const
1490
                                 restrict2
1491
{
1492
    if (skill == nullptr)
1493
        return getCastAction();
1494
1495
    if (mHorseId != 0)
1496
        return skill->castingRideAction;
1497
    if (mMap != nullptr)
1498
    {
1499
        const unsigned char mask = mMap->getBlockMask(mX, mY);
1500
        if ((mask & BlockMask::AIR) != 0)
1501
            return skill->castingSkyAction;
1502
        else if ((mask & BlockMask::WATER) != 0)
1503
            return skill->castingWaterAction;
1504
    }
1505
    return skill->castingAction;
1506
}
1507
1508
#define getSpriteAction(func, action) \
1509
    std::string Being::get##func##Action() const restrict2\
1510
{ \
1511
    if (mHorseId != 0) \
1512
        return SpriteAction::action##RIDE; \
1513
    if (mMap) \
1514
    { \
1515
        const unsigned char mask = mMap->getBlockMask(mX, mY); \
1516
        if (mask & BlockMask::AIR) \
1517
            return SpriteAction::action##SKY; \
1518
        else if (mask & BlockMask::WATER) \
1519
            return SpriteAction::action##WATER; \
1520
    } \
1521
    return SpriteAction::action; \
1522
}
1523
1524
getSpriteAction(Attack, ATTACK)
1525
getSpriteAction(Cast, CAST)
1526
getSpriteAction(Dead, DEAD)
1527
getSpriteAction(Spawn, SPAWN)
1528
1529
std::string Being::getStandAction() const restrict2
1530
{
1531
    if (mHorseId != 0)
1532
        return SpriteAction::STANDRIDE;
1533
    if (mMap != nullptr)
1534
    {
1535
        const unsigned char mask = mMap->getBlockMask(mX, mY);
1536
        if (mTrickDead)
1537
        {
1538
            if ((mask & BlockMask::AIR) != 0)
1539
                return SpriteAction::DEADSKY;
1540
            else if ((mask & BlockMask::WATER) != 0)
1541
                return SpriteAction::DEADWATER;
1542
            else
1543
                return SpriteAction::DEAD;
1544
        }
1545
        if ((mask & BlockMask::AIR) != 0)
1546
            return SpriteAction::STANDSKY;
1547
        else if ((mask & BlockMask::WATER) != 0)
1548
            return SpriteAction::STANDWATER;
1549
    }
1550
    return SpriteAction::STAND;
1551
}
1552
1553
void Being::setAction(const BeingActionT &restrict action,
1554
                      const int attackId) restrict2
1555
{
1556
    std::string currentAction = SpriteAction::INVALID;
1557
1558
    switch (action)
1559
    {
1560
        case BeingAction::MOVE:
1561
            if (mInfo != nullptr)
1562
            {
1563
                playSfx(mInfo->getSound(
1564
                    ItemSoundEvent::MOVE), nullptr, true, mX, mY);
1565
            }
1566
            currentAction = getMoveAction();
1567
            // Note: When adding a run action,
1568
            // Differentiate walk and run with action name,
1569
            // while using only the ACTION_MOVE.
1570
            break;
1571
        case BeingAction::SIT:
1572
            currentAction = getSitAction();
1573
            if (mInfo != nullptr)
1574
            {
1575
                ItemSoundEvent::Type event;
1576
                if (currentAction == SpriteAction::SITTOP)
1577
                    event = ItemSoundEvent::SITTOP;
1578
                else
1579
                    event = ItemSoundEvent::SIT;
1580
                playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
1581
            }
1582
            break;
1583
        case BeingAction::ATTACK:
1584
            if (mEquippedWeapon != nullptr)
1585
            {
1586
                currentAction = getWeaponAttackAction(mEquippedWeapon);
1587
                reset();
1588
            }
1589
            else
1590
            {
1591
                if (mInfo == nullptr || mInfo->getAttack(attackId) == nullptr)
1592
                    break;
1593
1594
                currentAction = getAttackAction(mInfo->getAttack(attackId));
1595
                reset();
1596
1597
                // attack particle effect
1598
                if (ParticleEngine::enabled && (effectManager != nullptr))
1599
                {
1600
                    const int effectId = mInfo->getAttack(attackId)->mEffectId;
1601
                    if (effectId >= 0)
1602
                    {
1603
                        effectManager->triggerDirection(effectId,
1604
                            this,
1605
                            mSpriteDirection);
1606
                    }
1607
                }
1608
            }
1609
            break;
1610
        case BeingAction::CAST:
1611
            if (skillDialog != nullptr)
1612
            {
1613
                const SkillInfo *restrict const info =
1614
                    skillDialog->getSkill(attackId);
1615
                currentAction = getCastAction(info);
1616
            }
1617
            break;
1618
        case BeingAction::HURT:
1619
            if (mInfo != nullptr)
1620
            {
1621
                playSfx(mInfo->getSound(ItemSoundEvent::HURT),
1622
                    this, false, mX, mY);
1623
            }
1624
            break;
1625
        case BeingAction::DEAD:
1626
            currentAction = getDeadAction();
1627
            if (mInfo != nullptr)
1628
            {
1629
                playSfx(mInfo->getSound(ItemSoundEvent::DIE),
1630
                    this,
1631
                    false,
1632
                    mX, mY);
1633
                if (mType == ActorType::Monster ||
1634
                    mType == ActorType::Npc ||
1635
                    mType == ActorType::SkillUnit)
1636
                {
1637
                    mYDiff = mInfo->getDeadSortOffsetY();
1638
                }
1639
            }
1640
            break;
1641
        case BeingAction::STAND:
1642
            currentAction = getStandAction();
1643
            break;
1644
        case BeingAction::SPAWN:
1645
            if (mInfo != nullptr)
1646
            {
1647
                playSfx(mInfo->getSound(ItemSoundEvent::SPAWN),
1648
                    nullptr, true, mX, mY);
1649
            }
1650
            currentAction = getSpawnAction();
1651
            break;
1652
        case BeingAction::PRESTAND:
1653
            break;
1654
        default:
1655
            logger->log("Being::setAction unknown action: "
1656
                + toString(CAST_U32(action)));
1657
            break;
1658
    }
1659
1660
    if (currentAction != SpriteAction::INVALID)
1661
    {
1662
        mSpriteAction = currentAction;
1663
        play(currentAction);
1664
        if (mEmotionSprite != nullptr)
1665
            mEmotionSprite->play(currentAction);
1666
        if (mAnimationEffect != nullptr)
1667
            mAnimationEffect->play(currentAction);
1668
        for_each_badges()
1669
        {
1670
            AnimatedSprite *const sprite = mBadges[f];
1671
            if (sprite != nullptr)
1672
                sprite->play(currentAction);
1673
        }
1674
        for_each_horses(mDownHorseSprites)
1675
            (*it)->play(currentAction);
1676
        for_each_horses(mUpHorseSprites)
1677
            (*it)->play(currentAction);
1678
        mAction = action;
1679
    }
1680
1681
    if (currentAction != SpriteAction::MOVE
1682
        && currentAction != SpriteAction::FLY
1683
        && currentAction != SpriteAction::SWIM)
1684
    {
1685
        mActionTime = tick_time;
1686
    }
1687
}
1688
1689
void Being::setDirection(const uint8_t direction) restrict2
1690
{
1691
    if (mDirection == direction)
1692
        return;
1693
1694
    mDirection = direction;
1695
1696
    mDirectionDelayed = 0;
1697
1698
    // if the direction does not change much, keep the common component
1699
    int mFaceDirection = mDirection & direction;
1700
    if (mFaceDirection == 0)
1701
        mFaceDirection = direction;
1702
1703
    SpriteDirection::Type dir;
1704
    if ((mFaceDirection & BeingDirection::UP) != 0)
1705
    {
1706
        if ((mFaceDirection & BeingDirection::LEFT) != 0)
1707
            dir = SpriteDirection::UPLEFT;
1708
        else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1709
            dir = SpriteDirection::UPRIGHT;
1710
        else
1711
            dir = SpriteDirection::UP;
1712
    }
1713
    else if ((mFaceDirection & BeingDirection::DOWN) != 0)
1714
    {
1715
        if ((mFaceDirection & BeingDirection::LEFT) != 0)
1716
            dir = SpriteDirection::DOWNLEFT;
1717
        else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1718
            dir = SpriteDirection::DOWNRIGHT;
1719
        else
1720
            dir = SpriteDirection::DOWN;
1721
    }
1722
    else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1723
    {
1724
        dir = SpriteDirection::RIGHT;
1725
    }
1726
    else
1727
    {
1728
        dir = SpriteDirection::LEFT;
1729
    }
1730
    mSpriteDirection = dir;
1731
1732
    CompoundSprite::setSpriteDirection(dir);
1733
    if (mEmotionSprite != nullptr)
1734
        mEmotionSprite->setSpriteDirection(dir);
1735
    if (mAnimationEffect != nullptr)
1736
        mAnimationEffect->setSpriteDirection(dir);
1737
1738
    for_each_badges()
1739
    {
1740
        AnimatedSprite *const sprite = mBadges[f];
1741
        if (sprite != nullptr)
1742
            sprite->setSpriteDirection(dir);
1743
    }
1744
1745
    for_each_horses(mDownHorseSprites)
1746
        (*it)->setSpriteDirection(dir);
1747
    for_each_horses(mUpHorseSprites)
1748
        (*it)->setSpriteDirection(dir);
1749
    recalcSpritesOrder();
1750
}
1751
1752
uint8_t Being::calcDirection() const restrict2
1753
{
1754
    uint8_t dir = 0;
1755
    if (mDest.x > mX)
1756
        dir |= BeingDirection::RIGHT;
1757
    else if (mDest.x < mX)
1758
        dir |= BeingDirection::LEFT;
1759
    if (mDest.y > mY)
1760
        dir |= BeingDirection::DOWN;
1761
    else if (mDest.y < mY)
1762
        dir |= BeingDirection::UP;
1763
    return dir;
1764
}
1765
1766
uint8_t Being::calcDirection(const int dstX,
1767
                             const int dstY) const restrict2
1768
{
1769
    uint8_t dir = 0;
1770
    if (dstX > mX)
1771
        dir |= BeingDirection::RIGHT;
1772
    else if (dstX < mX)
1773
        dir |= BeingDirection::LEFT;
1774
    if (dstY > mY)
1775
        dir |= BeingDirection::DOWN;
1776
    else if (dstY < mY)
1777
        dir |= BeingDirection::UP;
1778
    return dir;
1779
}
1780
1781
void Being::nextTile() restrict2
1782
{
1783
    if (mPath.empty())
1784
    {
1785
        mAction = BeingAction::PRESTAND;
1786
        mPreStandTime = tick_time;
1787
        return;
1788
    }
1789
1790
    const Position pos = mPath.front();
1791
    mPath.pop_front();
1792
1793
    const uint8_t dir = calcDirection(pos.x, pos.y);
1794
    if (dir != 0u)
1795
        setDirection(dir);
1796
1797
    if (mMap == nullptr ||
1798
        !mMap->getWalk(pos.x, pos.y, getBlockWalkMask()))
1799
    {
1800
        setAction(BeingAction::STAND, 0);
1801
        return;
1802
    }
1803
1804
    mActionTime += mSpeed / 10;
1805
    if ((mType != ActorType::Player || mUseDiagonal)
1806
        && mX != pos.x && mY != pos.y)
1807
    {
1808
        mSpeed = mWalkSpeed * 14 / 10;
1809
    }
1810
    else
1811
    {
1812
        mSpeed = mWalkSpeed;
1813
    }
1814
1815
    if (mX != pos.x || mY != pos.y)
1816
    {
1817
        mOldHeight = mMap->getHeightOffset(mX, mY);
1818
        if (mReachable == Reachable::REACH_NO &&
1819
            mMap->getBlockMask(mX, mY) != mMap->getBlockMask(pos.x, pos.y))
1820
        {
1821
            mReachable = Reachable::REACH_UNKNOWN;
1822
        }
1823
    }
1824
    mX = pos.x;
1825
    mY = pos.y;
1826
    const uint8_t height = mMap->getHeightOffset(mX, mY);
1827
    mPixelOffsetY = height - mOldHeight;
1828
    mFixedOffsetY = height;
1829
    mNeedPosUpdate = true;
1830
    setAction(BeingAction::MOVE, 0);
1831
}
1832
1833
void Being::logic() restrict2
1834
{
1835
    BLOCK_START("Being::logic")
1836
    if (A_UNLIKELY(mSpeechTime != 0))
1837
    {
1838
        mSpeechTime--;
1839
        if (mSpeechTime == 0 && mText != nullptr)
1840
            delete2(mText)
1841
    }
1842
1843
    if (A_UNLIKELY(mOwner != nullptr))
1844
    {
1845
        if (mType == ActorType::Homunculus ||
1846
            mType == ActorType::Mercenary)
1847
        {
1848
            botLogic();
1849
        }
1850
    }
1851
1852
    const int time = tick_time * MILLISECONDS_IN_A_TICK;
1853
    if (mEmotionSprite != nullptr)
1854
        mEmotionSprite->update(time);
1855
    for_each_horses(mDownHorseSprites)
1856
        (*it)->update(time);
1857
    for_each_horses(mUpHorseSprites)
1858
        (*it)->update(time);
1859
1860
    if (A_UNLIKELY(mCastEndTime != 0 && mCastEndTime < tick_time))
1861
    {
1862
        mCastEndTime = 0;
1863
        delete2(mCastingEffect);
1864
    }
1865
1866
    if (A_UNLIKELY(mAnimationEffect))
1867
    {
1868
        mAnimationEffect->update(time);
1869
        if (mAnimationEffect->isTerminated())
1870
            delete2(mAnimationEffect)
1871
    }
1872
    if (A_UNLIKELY(mCastingEffect))
1873
    {
1874
        mCastingEffect->update(time);
1875
        if (mCastingEffect->isTerminated())
1876
            delete2(mCastingEffect)
1877
    }
1878
    for_each_badges()
1879
    {
1880
        AnimatedSprite *restrict const sprite = mBadges[f];
1881
        if (sprite != nullptr)
1882
            sprite->update(time);
1883
    }
1884
1885
    int frameCount = CAST_S32(getFrameCount());
1886
1887
    switch (mAction)
1888
    {
1889
        case BeingAction::STAND:
1890
        case BeingAction::SIT:
1891
        case BeingAction::DEAD:
1892
        case BeingAction::HURT:
1893
        case BeingAction::SPAWN:
1894
        case BeingAction::CAST:
1895
        default:
1896
            break;
1897
1898
        case BeingAction::MOVE:
1899
        {
1900
            if (get_elapsed_time(mActionTime) >= mSpeed)
1901
                nextTile();
1902
            break;
1903
        }
1904
1905
        case BeingAction::ATTACK:
1906
        {
1907
            if (mActionTime == 0)
1908
                break;
1909
1910
            int curFrame = 0;
1911
            if (mAttackSpeed != 0)
1912
            {
1913
                curFrame = (get_elapsed_time(mActionTime) * frameCount)
1914
                    / mAttackSpeed;
1915
            }
1916
1917
            if (this == localPlayer && curFrame >= frameCount)
1918
                nextTile();
1919
1920
            break;
1921
        }
1922
1923
        case BeingAction::PRESTAND:
1924
        {
1925
            if (get_elapsed_time1(mPreStandTime) > 10)
1926
                setAction(BeingAction::STAND, 0);
1927
            break;
1928
        }
1929
    }
1930
1931
    if (mAction == BeingAction::MOVE || mNeedPosUpdate)
1932
    {
1933
        const int xOffset = getOffset<BeingDirection::LEFT,
1934
            BeingDirection::RIGHT>();
1935
        const int yOffset = getOffset<BeingDirection::UP,
1936
            BeingDirection::DOWN>();
1937
        int offset = xOffset;
1938
        if (offset == 0)
1939
            offset = yOffset;
1940
1941
        if ((xOffset == 0) && (yOffset == 0))
1942
            mNeedPosUpdate = false;
1943
1944
        const int halfTile = mapTileSize / 2;
1945
        const float offset2 = static_cast<float>(
1946
            mPixelOffsetY * abs(offset)) / 2;
1947
//        mSortOffsetY = (mOldHeight - mFixedOffsetY + mPixelOffsetY)
1948
//            * halfTile - offset2;
1949
        mSortOffsetY = 0;
1950
        const float yOffset3 = (mY + 1) * mapTileSize + yOffset
1951
            - (mOldHeight + mPixelOffsetY) * halfTile + offset2;
1952
1953
        // Update pixel coordinates
1954
        setPixelPositionF(static_cast<float>(mX * mapTileSize
1955
            + mapTileSize / 2 + xOffset),
1956
            yOffset3,
1957
            0.0F);
1958
    }
1959
1960
    if (A_UNLIKELY(mEmotionSprite))
1961
    {
1962
        mEmotionTime--;
1963
        if (mEmotionTime == 0)
1964
            delete2(mEmotionSprite)
1965
    }
1966
1967
    ActorSprite::logic();
1968
1969
    if (frameCount < 10)
1970
        frameCount = 10;
1971
1972
    if (A_UNLIKELY(!isAlive() &&
1973
        mSpeed != 0 &&
1974
        gameHandler->removeDeadBeings() &&
1975
        get_elapsed_time(mActionTime) / mSpeed >= frameCount))
1976
    {
1977
        if (mType != ActorType::Player && (actorManager != nullptr))
1978
            actorManager->destroy(this);
1979
    }
1980
1981
    const SoundInfo *restrict const sound = mNextSound.sound;
1982
    if (A_UNLIKELY(sound))
1983
    {
1984
        const int time2 = tick_time;
1985
        if (time2 > mNextSound.time)
1986
        {
1987
            soundManager.playSfx(sound->sound,
1988
                mNextSound.x,
1989
                mNextSound.y);
1990
            mNextSound.sound = nullptr;
1991
            mNextSound.time = time2 + sound->delay;
1992
        }
1993
    }
1994
1995
    BLOCK_END("Being::logic")
1996
}
1997
1998
void Being::botLogic() restrict2
1999
{
2000
    if ((mOwner == nullptr) || (mMap == nullptr) || (mInfo == nullptr))
2001
        return;
2002
2003
    const int time = tick_time;
2004
    const int thinkTime = mInfo->getThinkTime();
2005
    if (abs(CAST_S32(mMoveTime) - time) < thinkTime)
2006
        return;
2007
2008
    mMoveTime = time;
2009
2010
    int dstX = mOwner->mX;
2011
    int dstY = mOwner->mY;
2012
    const int warpDist = mInfo->getWarpDist();
2013
    const int divX = abs(dstX - mX);
2014
    const int divY = abs(dstY - mY);
2015
2016
    if (divX >= warpDist || divY >= warpDist)
2017
    {
2018
        if (mType == ActorType::Homunculus)
2019
            homunculusHandler->moveToMaster();
2020
        else
2021
            mercenaryHandler->moveToMaster();
2022
        mBotAi = true;
2023
        return;
2024
    }
2025
    if (!mBotAi)
2026
        return;
2027
    if (mAction == BeingAction::MOVE)
2028
    {
2029
        if (mOwner->mAction == BeingAction::MOVE)
2030
        {
2031
            updateBotFollow(dstX, dstY,
2032
                divX, divY);
2033
        }
2034
        return;
2035
    }
2036
2037
    switch (mOwner->mAction)
2038
    {
2039
        case BeingAction::MOVE:
2040
        case BeingAction::PRESTAND:
2041
            updateBotFollow(dstX, dstY,
2042
                divX, divY);
2043
            break;
2044
        case BeingAction::STAND:
2045
        case BeingAction::SPAWN:
2046
            botFixOffset(dstX, dstY);
2047
            moveBotTo(dstX, dstY);
2048
            break;
2049
        case BeingAction::ATTACK:
2050
        {
2051
            const Being *const target = localPlayer->getTarget();
2052
            if (target == nullptr)
2053
                return;
2054
            const BeingId targetId = target->getId();
2055
            if (mType == ActorType::Homunculus)
2056
            {
2057
                homunculusHandler->attack(targetId,
2058
                    Keep_true);
2059
            }
2060
            else
2061
            {
2062
                mercenaryHandler->attack(targetId,
2063
                    Keep_true);
2064
            }
2065
            break;
2066
        }
2067
        case BeingAction::SIT:
2068
            botFixOffset(dstX, dstY);
2069
            moveBotTo(dstX, dstY);
2070
            break;
2071
        case BeingAction::DEAD:
2072
            botFixOffset(dstX, dstY);
2073
            moveBotTo(dstX, dstY);
2074
            break;
2075
        case BeingAction::CAST:
2076
        case BeingAction::HURT:
2077
        default:
2078
            break;
2079
    }
2080
}
2081
2082
void Being::botFixOffset(int &restrict dstX,
2083
                         int &restrict dstY) const
2084
{
2085
    if ((mInfo == nullptr) || (mOwner == nullptr))
2086
        return;
2087
2088
    int offsetX1;
2089
    int offsetY1;
2090
    switch (mOwner->getCurrentAction())
2091
    {
2092
        case BeingAction::SIT:
2093
            offsetX1 = mInfo->getSitOffsetX();
2094
            offsetY1 = mInfo->getSitOffsetY();
2095
            break;
2096
2097
        case BeingAction::MOVE:
2098
            offsetX1 = mInfo->getMoveOffsetX();
2099
            offsetY1 = mInfo->getMoveOffsetY();
2100
            break;
2101
2102
        case BeingAction::DEAD:
2103
            offsetX1 = mInfo->getDeadOffsetX();
2104
            offsetY1 = mInfo->getDeadOffsetY();
2105
            break;
2106
2107
        case BeingAction::ATTACK:
2108
            offsetX1 = mInfo->getAttackOffsetX();
2109
            offsetY1 = mInfo->getAttackOffsetY();
2110
            break;
2111
2112
        case BeingAction::SPAWN:
2113
        case BeingAction::HURT:
2114
        case BeingAction::STAND:
2115
        case BeingAction::PRESTAND:
2116
        case BeingAction::CAST:
2117
        default:
2118
            offsetX1 = mInfo->getTargetOffsetX();
2119
            offsetY1 = mInfo->getTargetOffsetY();
2120
            break;
2121
    }
2122
2123
    int offsetX = offsetX1;
2124
    int offsetY = offsetY1;
2125
    switch (mOwner->mDirection)
2126
    {
2127
        case BeingDirection::LEFT:
2128
            offsetX = -offsetY1;
2129
            offsetY = offsetX1;
2130
            break;
2131
        case BeingDirection::RIGHT:
2132
            offsetX = offsetY1;
2133
            offsetY = -offsetX1;
2134
            break;
2135
        case BeingDirection::UP:
2136
            offsetY = -offsetY;
2137
            offsetX = -offsetX;
2138
            break;
2139
        default:
2140
        case BeingDirection::DOWN:
2141
            break;
2142
    }
2143
    dstX += offsetX;
2144
    dstY += offsetY;
2145
    if (mMap != nullptr)
2146
    {
2147
        if (!mMap->getWalk(dstX, dstY, getBlockWalkMask()))
2148
        {
2149
            dstX = mOwner->mX;
2150
            dstY = mOwner->mY;
2151
        }
2152
    }
2153
}
2154
2155
void Being::updateBotFollow(int dstX,
2156
                            int dstY,
2157
                            const int divX,
2158
                            const int divY)
2159
{
2160
    const int followDist = mInfo->getStartFollowDist();
2161
    const int dist = mInfo->getFollowDist();
2162
    if (divX > followDist || divY > followDist)
2163
    {
2164
        if (dist > 0)
2165
        {
2166
            if (divX > followDist)
2167
            {
2168
                if (dstX > mX + dist)
2169
                    dstX -= dist;
2170
                else if (dstX + dist <= mX)
2171
                    dstX += dist;
2172
            }
2173
            else
2174
            {
2175
                dstX = mX;
2176
            }
2177
            if (divY > followDist)
2178
            {
2179
                if (dstY > mY + dist)
2180
                    dstY -= dist;
2181
                else if (dstX + dist <= mX)
2182
                    dstY += dist;
2183
            }
2184
            else
2185
            {
2186
                dstY = mY;
2187
            }
2188
        }
2189
        botFixOffset(dstX, dstY);
2190
        moveBotTo(dstX, dstY);
2191
    }
2192
}
2193
2194
void Being::moveBotTo(int dstX,
2195
                      int dstY)
2196
{
2197
    const int dstX0 = mOwner->mX;
2198
    const int dstY0 = mOwner->mY;
2199
    const unsigned char blockWalkMask = getBlockWalkMask();
2200
    if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2201
    {
2202
        if (dstX != dstX0)
2203
        {
2204
            dstX = dstX0;
2205
            if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2206
                dstY = dstY0;
2207
        }
2208
        else if (dstY != dstY0)
2209
        {
2210
            dstY = dstY0;
2211
            if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2212
                dstX = dstX0;
2213
        }
2214
    }
2215
    if (mX != dstX || mY != dstY)
2216
    {
2217
        if (mType == ActorType::Homunculus)
2218
            homunculusHandler->move(dstX, dstY);
2219
        else
2220
            mercenaryHandler->move(dstX, dstY);
2221
        return;
2222
    }
2223
    updateBotDirection(dstX, dstY);
2224
}
2225
2226
void Being::updateBotDirection(const int dstX,
2227
                               const int dstY)
2228
{
2229
    int directionType = 0;
2230
    switch (mOwner->getCurrentAction())
2231
    {
2232
        case BeingAction::STAND:
2233
        case BeingAction::MOVE:
2234
        case BeingAction::HURT:
2235
        case BeingAction::SPAWN:
2236
        case BeingAction::CAST:
2237
        case BeingAction::PRESTAND:
2238
        default:
2239
            directionType = mInfo->getDirectionType();
2240
            break;
2241
        case BeingAction::SIT:
2242
            directionType = mInfo->getSitDirectionType();
2243
            break;
2244
        case BeingAction::DEAD:
2245
            directionType = mInfo->getDeadDirectionType();
2246
            break;
2247
        case BeingAction::ATTACK:
2248
            directionType = mInfo->getAttackDirectionType();
2249
            break;
2250
    }
2251
2252
    uint8_t newDir = 0;
2253
    switch (directionType)
2254
    {
2255
        case 0:
2256
        default:
2257
            return;
2258
2259
        case 1:
2260
            newDir = mOwner->mDirection;
2261
            break;
2262
2263
        case 2:
2264
        {
2265
            const int dstX0 = mOwner->mX;
2266
            const int dstY0 = mOwner->mY;
2267
            if (dstX > dstX0)
2268
                newDir |= BeingDirection::LEFT;
2269
            else if (dstX < dstX0)
2270
                newDir |= BeingDirection::RIGHT;
2271
            if (dstY > dstY0)
2272
                newDir |= BeingDirection::UP;
2273
            else if (dstY < dstY0)
2274
                newDir |= BeingDirection::DOWN;
2275
            break;
2276
        }
2277
        case 3:
2278
        {
2279
            const int dstX0 = mOwner->mX;
2280
            const int dstY0 = mOwner->mY;
2281
            if (dstX > dstX0)
2282
                newDir |= BeingDirection::RIGHT;
2283
            else if (dstX < dstX0)
2284
                newDir |= BeingDirection::LEFT;
2285
            if (dstY > dstY0)
2286
                newDir |= BeingDirection::DOWN;
2287
            else if (dstY < dstY0)
2288
                newDir |= BeingDirection::UP;
2289
            break;
2290
        }
2291
        case 4:
2292
        {
2293
            const int dstX2 = mOwner->getLastAttackX();
2294
            const int dstY2 = mOwner->getLastAttackY();
2295
            if (dstX > dstX2)
2296
                newDir |= BeingDirection::LEFT;
2297
            else if (dstX < dstX2)
2298
                newDir |= BeingDirection::RIGHT;
2299
            if (dstY > dstY2)
2300
                newDir |= BeingDirection::UP;
2301
            else if (dstY < dstY2)
2302
                newDir |= BeingDirection::DOWN;
2303
            break;
2304
        }
2305
    }
2306
    if ((newDir != 0u) && newDir != mDirection)
2307
    {
2308
        if (mType == ActorType::Homunculus)
2309
            homunculusHandler->setDirection(newDir);
2310
        else
2311
            mercenaryHandler->setDirection(newDir);
2312
    }
2313
}
2314
2315
18
void Being::updateBadgesPosition()
2316
{
2317
18
    const int px = mPixelX - mapTileSize / 2;
2318
18
    const int py = mPixelY - mapTileSize * 2 - mapTileSize;
2319

36
    if (mShowBadges != BadgeDrawType::Hide &&
2320
18
        mBadgesCount != 0u)
2321
    {
2322
        if (mDispName != nullptr &&
2323
            gui != nullptr)
2324
        {
2325
            if (mShowBadges == BadgeDrawType::Right)
2326
            {
2327
                const Font *restrict const font = gui->getFont();
2328
                mBadgesX = mDispName->getX() + mDispName->getWidth();
2329
                mBadgesY = mDispName->getY() - font->getHeight();
2330
            }
2331
            else if (mShowBadges == BadgeDrawType::Bottom)
2332
            {
2333
                mBadgesX = px + 8 - mBadgesCount * 8;
2334
                if (mVisibleNamePos == VisibleNamePos::Bottom)
2335
                {
2336
                    mBadgesY = mDispName->getY();
2337
                }
2338
                else
2339
                {
2340
                    mBadgesY = py + settings.playerNameOffset + 16;
2341
                }
2342
            }
2343
            else
2344
            {
2345
                mBadgesX = px + 8 - mBadgesCount * 8;
2346
                if (mVisibleNamePos == VisibleNamePos::Top)
2347
                    mBadgesY = py - mDispName->getHeight();
2348
                else
2349
                    mBadgesY = py;
2350
            }
2351
        }
2352
        else
2353
        {
2354
            if (mShowBadges == BadgeDrawType::Right)
2355
            {
2356
                mBadgesX = px + settings.playerBadgeAtRightOffset;
2357
                mBadgesY = py;
2358
            }
2359
            else if (mShowBadges == BadgeDrawType::Bottom)
2360
            {
2361
                mBadgesX = px + 8 - mBadgesCount * 8;
2362
                const int height = settings.playerNameOffset;
2363
                if (mVisibleNamePos == VisibleNamePos::Bottom)
2364
                    mBadgesY = py + height;
2365
                else
2366
                    mBadgesY = py + height + 16;
2367
            }
2368
            else
2369
            {
2370
                mBadgesX = px + 8 - mBadgesCount * 8;
2371
                mBadgesY = py;
2372
            }
2373
        }
2374
    }
2375
18
}
2376
2377
void Being::drawEmotion(Graphics *restrict const graphics,
2378
                        const int offsetX,
2379
                        const int offsetY) const restrict2
2380
{
2381
    if (mErased)
2382
        return;
2383
2384
    const int px = mPixelX - offsetX - mapTileSize / 2;
2385
    const int py = mPixelY - offsetY - mapTileSize * 2 - mapTileSize;
2386
    if (mAnimationEffect != nullptr)
2387
        mAnimationEffect->draw(graphics, px, py);
2388
    if (mShowBadges != BadgeDrawType::Hide &&
2389
        mBadgesCount != 0u)
2390
    {
2391
        int x = mBadgesX - offsetX;
2392
        const int y = mBadgesY - offsetY;
2393
        for_each_badges()
2394
        {
2395
            const AnimatedSprite *restrict const sprite = mBadges[f];
2396
            if (sprite != nullptr)
2397
            {
2398
                sprite->draw(graphics, x, y);
2399
                x += 16;
2400
            }
2401
        }
2402
    }
2403
    if (mEmotionSprite != nullptr)
2404
        mEmotionSprite->draw(graphics, px, py);
2405
}
2406
2407
void Being::drawSpeech(const int offsetX,
2408
                       const int offsetY) restrict2
2409
{
2410
    if (mErased)
2411
        return;
2412
    if (mSpeech.empty())
2413
        return;
2414
2415
    const int px = mPixelX - offsetX;
2416
    const int py = mPixelY - offsetY;
2417
    const int speech = mSpeechType;
2418
2419
    // Draw speech above this being
2420
    if (mSpeechTime == 0)
2421
    {
2422
        if (mSpeechBubble != nullptr &&
2423
            mSpeechBubble->mVisible == Visible_true)
2424
        {
2425
            mSpeechBubble->setVisible(Visible_false);
2426
        }
2427
        mSpeech.clear();
2428
    }
2429
    else if (mSpeechTime > 0 && (speech == BeingSpeech::NAME_IN_BUBBLE ||
2430
             speech == BeingSpeech::NO_NAME_IN_BUBBLE))
2431
    {
2432
        delete2(mText)
2433
2434
        if (mSpeechBubble != nullptr)
2435
        {
2436
            mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
2437
                py - getHeight() - (mSpeechBubble->getHeight()));
2438
            mSpeechBubble->setVisible(Visible_true);
2439
        }
2440
    }
2441
    else if (mSpeechTime > 0 && speech == BeingSpeech::TEXT_OVERHEAD)
2442
    {
2443
        if (mSpeechBubble != nullptr)
2444
            mSpeechBubble->setVisible(Visible_false);
2445
2446
        if ((mText == nullptr) && (userPalette != nullptr))
2447
        {
2448
            mText = new Text(mSpeech,
2449
                mPixelX,
2450
                mPixelY - getHeight(),
2451
                Graphics::CENTER,
2452
                &theme->getColor(ThemeColorId::BUBBLE_TEXT, 255),
2453
                Speech_true,
2454
                nullptr);
2455
            mText->adviseXY(mPixelX,
2456
                (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
2457
                mMoveNames);
2458
        }
2459
    }
2460
    else if (speech == BeingSpeech::NO_SPEECH)
2461
    {
2462
        if (mSpeechBubble != nullptr)
2463
            mSpeechBubble->setVisible(Visible_false);
2464
        delete2(mText)
2465
    }
2466
}
2467
2468
template<signed char pos, signed char neg>
2469
int Being::getOffset() const restrict2
2470
{
2471
    // Check whether we're walking in the requested direction
2472
    if (mAction != BeingAction::MOVE || !(mDirection & (pos | neg)))
2473
        return 0;
2474
2475
    int offset = 0;
2476
2477
    if (mMap && mSpeed)
2478
    {
2479
        const int time = get_elapsed_time(mActionTime);
2480
        offset = (pos == BeingDirection::LEFT &&
2481
            neg == BeingDirection::RIGHT) ?
2482
            (time * mMap->getTileWidth() / mSpeed)
2483
            : (time * mMap->getTileHeight() / mSpeed);
2484
    }
2485
2486
    // We calculate the offset _from_ the _target_ location
2487
    offset -= mapTileSize;
2488
    if (offset > 0)
2489
        offset = 0;
2490
2491
    // Going into negative direction? Invert the offset.
2492
    if (mDirection & pos)
2493
        offset = -offset;
2494
2495
    if (offset > mapTileSize)
2496
        offset = mapTileSize;
2497
    if (offset < -mapTileSize)
2498
        offset = -mapTileSize;
2499
2500
    return offset;
2501
}
2502
2503
18
void Being::updateCoords() restrict2
2504
{
2505
18
    if (mDispName != nullptr)
2506
    {
2507
18
        int offsetX = mPixelX;
2508
18
        int offsetY = mPixelY;
2509
18
        if (mInfo != nullptr)
2510
        {
2511
            offsetX += mInfo->getNameOffsetX();
2512
            offsetY += mInfo->getNameOffsetY();
2513
        }
2514
        // Monster names show above the sprite instead of below it
2515

34
        if (mType == ActorType::Monster ||
2516
16
            mVisibleNamePos == VisibleNamePos::Top)
2517
        {
2518
4
            offsetY += - settings.playerNameOffset - mDispName->getHeight();
2519
        }
2520
18
        mDispName->adviseXY(offsetX, offsetY, mMoveNames);
2521
    }
2522
18
    updateBadgesPosition();
2523
18
}
2524
2525
void Being::optionChanged(const std::string &restrict value) restrict2
2526
{
2527
    if (mType == ActorType::Player && value == "visiblenames")
2528
    {
2529
        setShowName(config.getIntValue("visiblenames") == VisibleName::Show);
2530
        updateBadgesPosition();
2531
    }
2532
}
2533
2534
void Being::flashName(const int time) restrict2
2535
{
2536
    if (mDispName != nullptr)
2537
        mDispName->flash(time);
2538
}
2539
2540
std::string Being::getGenderSignWithSpace() const restrict2
2541
{
2542
    const std::string &restrict str = getGenderSign();
2543
    if (str.empty())
2544
        return str;
2545
    else
2546
        return std::string(" ").append(str);
2547
}
2548
2549
std::string Being::getGenderSign() const restrict2
2550
{
2551
    std::string str;
2552
    if (mShowGender)
2553
    {
2554
        if (getGender() == Gender::FEMALE)
2555
            str = "\u2640";
2556
        else if (getGender() == Gender::MALE)
2557
            str = "\u2642";
2558
    }
2559
    if (mShowPlayersStatus &&
2560
        mShowBadges == BadgeDrawType::Hide)
2561
    {
2562
        if (mShop)
2563
            str.append("$");
2564
        if (mAway)
2565
        {
2566
            // TRANSLATORS: this away status writed in player nick
2567
            str.append(_("A"));
2568
        }
2569
        else if (mInactive)
2570
        {
2571
            // TRANSLATORS: this inactive status writed in player nick
2572
            str.append(_("I"));
2573
        }
2574
    }
2575
    return str;
2576
}
2577
2578
220
void Being::showName() restrict2
2579
{
2580
440
    if (mName.empty())
2581
202
        return;
2582
2583
36
    delete2(mDispName);
2584
2585

18
    if (mHideErased && playerRelations.getRelation(mName) == Relation::ERASED)
2586
        return;
2587
2588
54
    std::string displayName(mName);
2589
2590

18
    if (mType != ActorType::Monster && (mShowGender || mShowLevel))
2591
    {
2592
        displayName.append(" ");
2593
        if (mShowLevel && getLevel() != 0)
2594
            displayName.append(toString(getLevel()));
2595
2596
        displayName.append(getGenderSign());
2597
    }
2598
2599
18
    if (mType == ActorType::Monster)
2600
    {
2601

8
        if (config.getBoolValue("showMonstersTakedDamage"))
2602

8
            displayName.append(", ").append(toString(getDamageTaken()));
2603
    }
2604
2605
18
    Font *font = nullptr;
2606

36
    if ((localPlayer != nullptr) && localPlayer->getTarget() == this
2607

22
        && mType != ActorType::Monster)
2608
    {
2609
2
        font = boldFont;
2610
    }
2611
16
    else if (mType == ActorType::Player
2612


30
             && !playerRelations.isGoodName(this) && (gui != nullptr))
2613
    {
2614
        font = gui->getSecureFont();
2615
    }
2616
2617
18
    if (mInfo != nullptr)
2618
    {
2619
        mDispName = new FlashText(displayName,
2620
            mPixelX + mInfo->getNameOffsetX(),
2621
            mPixelY + mInfo->getNameOffsetY(),
2622
            Graphics::CENTER,
2623
            mNameColor,
2624
            font);
2625
    }
2626
    else
2627
    {
2628
18
        mDispName = new FlashText(displayName,
2629
            mPixelX,
2630
            mPixelY,
2631
            Graphics::CENTER,
2632
            mNameColor,
2633

18
            font);
2634
    }
2635
2636
18
    updateCoords();
2637
}
2638
2639
142
void Being::setDefaultNameColor(const UserColorIdT defaultColor) restrict2
2640
{
2641

142
    switch (mTeamId)
2642
    {
2643
142
        case 0:
2644
        default:
2645
142
            mNameColor = &userPalette->getColor(defaultColor,
2646
142
                255U);
2647
142
            break;
2648
        case 1:
2649
            mNameColor = &userPalette->getColor(UserColorId::TEAM1,
2650
                255U);
2651
            break;
2652
        case 2:
2653
            mNameColor = &userPalette->getColor(UserColorId::TEAM2,
2654
                255U);
2655
            break;
2656
        case 3:
2657
            mNameColor = &userPalette->getColor(UserColorId::TEAM3,
2658
                255U);
2659
            break;
2660
    }
2661
142
}
2662
2663
218
void Being::updateColors()
2664
{
2665
218
    if (userPalette != nullptr)
2666
    {
2667
142
        if (mType == ActorType::Monster)
2668
        {
2669
            setDefaultNameColor(UserColorId::MONSTER);
2670
            mTextColor = &userPalette->getColor(UserColorId::MONSTER,
2671
                255U);
2672
        }
2673
142
        else if (mType == ActorType::Npc)
2674
        {
2675
            setDefaultNameColor(UserColorId::NPC);
2676
            mTextColor = &userPalette->getColor(UserColorId::NPC,
2677
                255U);
2678
        }
2679
142
        else if (mType == ActorType::Pet)
2680
        {
2681
            setDefaultNameColor(UserColorId::PET);
2682
            mTextColor = &userPalette->getColor(UserColorId::PET,
2683
                255U);
2684
        }
2685
142
        else if (mType == ActorType::Homunculus)
2686
        {
2687
            setDefaultNameColor(UserColorId::HOMUNCULUS);
2688
            mTextColor = &userPalette->getColor(UserColorId::HOMUNCULUS,
2689
                255U);
2690
        }
2691
142
        else if (mType == ActorType::SkillUnit)
2692
        {
2693
            setDefaultNameColor(UserColorId::SKILLUNIT);
2694
            mTextColor = &userPalette->getColor(UserColorId::SKILLUNIT,
2695
                255U);
2696
        }
2697
142
        else if (this == localPlayer)
2698
        {
2699
            mNameColor = &userPalette->getColor(UserColorId::SELF, 255U);
2700
            mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2701
        }
2702
        else
2703
        {
2704
142
            mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2705
2706
142
            if (playerRelations.getRelation(mName) != Relation::ERASED)
2707
142
                mErased = false;
2708
            else
2709
                mErased = true;
2710
2711
142
            if (mIsGM)
2712
            {
2713
                mTextColor = &userPalette->getColor(UserColorId::GM,
2714
                    255U);
2715
                mNameColor = &userPalette->getColor(UserColorId::GM,
2716
                    255U);
2717
            }
2718
142
            else if (mEnemy)
2719
            {
2720
                mNameColor = &userPalette->getColor(UserColorId::ENEMY,
2721
                255U);
2722
            }
2723
142
            else if ((mParty != nullptr) && (localPlayer != nullptr)
2724

142
                     && mParty == localPlayer->getParty())
2725
            {
2726
                mNameColor = &userPalette->getColor(UserColorId::PARTY,
2727
                    255U);
2728
            }
2729
142
            else if ((localPlayer != nullptr) && (getGuild() != nullptr)
2730

142
                     && getGuild() == localPlayer->getGuild())
2731
            {
2732
                mNameColor = &userPalette->getColor(UserColorId::GUILD,
2733
                    255U);
2734
            }
2735
142
            else if (playerRelations.getRelation(mName) == Relation::FRIEND)
2736
            {
2737
                mNameColor = &userPalette->getColor(UserColorId::FRIEND,
2738
                    255U);
2739
            }
2740
142
            else if (playerRelations.getRelation(mName) ==
2741
                     Relation::DISREGARDED
2742

142
                     || playerRelations.getRelation(mName) ==
2743
                     Relation::BLACKLISTED)
2744
            {
2745
                mNameColor = &userPalette->getColor(UserColorId::DISREGARDED,
2746
                    255U);
2747
            }
2748
142
            else if (playerRelations.getRelation(mName)
2749

284
                     == Relation::IGNORED ||
2750
142
                     playerRelations.getRelation(mName) == Relation::ENEMY2)
2751
            {
2752
                mNameColor = &userPalette->getColor(UserColorId::IGNORED,
2753
                    255U);
2754
            }
2755
142
            else if (playerRelations.getRelation(mName) == Relation::ERASED)
2756
            {
2757
                mNameColor = &userPalette->getColor(UserColorId::ERASED,
2758
                    255U);
2759
            }
2760
            else
2761
            {
2762
142
                setDefaultNameColor(UserColorId::PC);
2763
            }
2764
        }
2765
2766
142
        if (mDispName != nullptr)
2767
            mDispName->setColor(mNameColor);
2768
    }
2769
218
}
2770
2771
void Being::updateSprite(const unsigned int slot,
2772
                         const int id,
2773
                         const std::string &restrict color) restrict2
2774
{
2775
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2776
        return;
2777
2778
    if (slot >= CAST_U32(mSlots.size()))
2779
        mSlots.resize(slot + 1, BeingSlot());
2780
2781
    if ((slot != 0u) && mSlots[slot].spriteId == id)
2782
        return;
2783
    setSpriteColor(slot,
2784
        id,
2785
        color);
2786
}
2787
2788
// set sprite id, reset colors, reset cards
2789
void Being::setSpriteId(const unsigned int slot,
2790
                        const int id) restrict2
2791
{
2792
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2793
        return;
2794
2795
    if (slot >= CAST_U32(mSprites.size()))
2796
        ensureSize(slot + 1);
2797
2798
    if (slot >= CAST_U32(mSlots.size()))
2799
        mSlots.resize(slot + 1, BeingSlot());
2800
2801
    // id = 0 means unequip
2802
    if (id == 0)
2803
    {
2804
        removeSprite(slot);
2805
        mSpriteDraw[slot] = 0;
2806
2807
        const int id1 = mSlots[slot].spriteId;
2808
        if (id1 != 0)
2809
            removeItemParticles(id1);
2810
    }
2811
    else
2812
    {
2813
        const ItemInfo &info = ItemDB::get(id);
2814
        const std::string &restrict filename = info.getSprite(
2815
            mGender, mSubType);
2816
        int lastTime = 0;
2817
        int startTime = 0;
2818
        AnimatedSprite *restrict equipmentSprite = nullptr;
2819
2820
        if (!filename.empty())
2821
        {
2822
            equipmentSprite = AnimatedSprite::delayedLoad(
2823
                pathJoin(paths.getStringValue("sprites"), filename),
2824
                0);
2825
        }
2826
2827
        if (equipmentSprite != nullptr)
2828
        {
2829
            equipmentSprite->setSpriteDirection(getSpriteDirection());
2830
            startTime = getStartTime();
2831
            lastTime = getLastTime();
2832
        }
2833
2834
        CompoundSprite::setSprite(slot, equipmentSprite);
2835
        mSpriteDraw[slot] = id;
2836
2837
        addItemParticles(id, info.getDisplay());
2838
2839
        setAction(mAction, 0);
2840
        if (equipmentSprite != nullptr)
2841
        {
2842
            if (lastTime > 0)
2843
            {
2844
                equipmentSprite->setLastTime(startTime);
2845
                equipmentSprite->update(lastTime);
2846
            }
2847
        }
2848
    }
2849
2850
    BeingSlot &beingSlot = mSlots[slot];
2851
    beingSlot.spriteId = id;
2852
    beingSlot.color.clear();
2853
    beingSlot.colorId = ItemColor_one;
2854
    beingSlot.cardsId = CardsList(nullptr);
2855
    recalcSpritesOrder();
2856
    if (beingEquipmentWindow != nullptr)
2857
        beingEquipmentWindow->updateBeing(this);
2858
}
2859
2860
// reset sprite id, reset colors, reset cards
2861
void Being::unSetSprite(const unsigned int slot) restrict2
2862
{
2863
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2864
        return;
2865
2866
    if (slot >= CAST_U32(mSprites.size()))
2867
        ensureSize(slot + 1);
2868
2869
    if (slot >= CAST_U32(mSlots.size()))
2870
        mSlots.resize(slot + 1, BeingSlot());
2871
2872
    removeSprite(slot);
2873
    mSpriteDraw[slot] = 0;
2874
2875
    BeingSlot &beingSlot = mSlots[slot];
2876
    const int id1 = beingSlot.spriteId;
2877
    if (id1 != 0)
2878
        removeItemParticles(id1);
2879
2880
    beingSlot.spriteId = 0;
2881
    beingSlot.color.clear();
2882
    beingSlot.colorId = ItemColor_one;
2883
    beingSlot.cardsId = CardsList(nullptr);
2884
    recalcSpritesOrder();
2885
    if (beingEquipmentWindow != nullptr)
2886
        beingEquipmentWindow->updateBeing(this);
2887
}
2888
2889
// set sprite id, use color string, reset cards
2890
void Being::setSpriteColor(const unsigned int slot,
2891
                           const int id,
2892
                           const std::string &color) restrict2
2893
{
2894
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2895
        return;
2896
2897
    if (slot >= CAST_U32(mSprites.size()))
2898
        ensureSize(slot + 1);
2899
2900
    if (slot >= CAST_U32(mSlots.size()))
2901
        mSlots.resize(slot + 1, BeingSlot());
2902
2903
    // disabled for now, because it may broke replace/reorder sprites logic
2904
//    if (slot && mSlots[slot].spriteId == id)
2905
//        return;
2906
2907
    // id = 0 means unequip
2908
    if (id == 0)
2909
    {
2910
        removeSprite(slot);
2911
        mSpriteDraw[slot] = 0;
2912
2913
        const int id1 = mSlots[slot].spriteId;
2914
        if (id1 != 0)
2915
            removeItemParticles(id1);
2916
    }
2917
    else
2918
    {
2919
        const ItemInfo &info = ItemDB::get(id);
2920
        const std::string &restrict filename = info.getSprite(
2921
            mGender, mSubType);
2922
        int lastTime = 0;
2923
        int startTime = 0;
2924
        AnimatedSprite *restrict equipmentSprite = nullptr;
2925
2926
        if (!filename.empty())
2927
        {
2928
            equipmentSprite = AnimatedSprite::delayedLoad(
2929
                pathJoin(paths.getStringValue("sprites"),
2930
                combineDye(filename, color)),
2931
                0);
2932
        }
2933
2934
        if (equipmentSprite != nullptr)
2935
        {
2936
            equipmentSprite->setSpriteDirection(getSpriteDirection());
2937
            startTime = getStartTime();
2938
            lastTime = getLastTime();
2939
        }
2940
2941
        CompoundSprite::setSprite(slot, equipmentSprite);
2942
        mSpriteDraw[slot] = id;
2943
        addItemParticles(id, info.getDisplay());
2944
2945
        setAction(mAction, 0);
2946
        if (equipmentSprite != nullptr)
2947
        {
2948
            if (lastTime > 0)
2949
            {
2950
                equipmentSprite->setLastTime(startTime);
2951
                equipmentSprite->update(lastTime);
2952
            }
2953
        }
2954
    }
2955
2956
    BeingSlot &beingSlot = mSlots[slot];
2957
    beingSlot.spriteId = id;
2958
    beingSlot.color = color;
2959
    beingSlot.colorId = ItemColor_one;
2960
    beingSlot.cardsId = CardsList(nullptr);
2961
    recalcSpritesOrder();
2962
    if (beingEquipmentWindow != nullptr)
2963
        beingEquipmentWindow->updateBeing(this);
2964
}
2965
2966
// set sprite id, use color id, reset cards
2967
void Being::setSpriteColorId(const unsigned int slot,
2968
                             const int id,
2969
                             ItemColor colorId) restrict2
2970
{
2971
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2972
        return;
2973
2974
    if (slot >= CAST_U32(mSprites.size()))
2975
        ensureSize(slot + 1);
2976
2977
    if (slot >= CAST_U32(mSlots.size()))
2978
        mSlots.resize(slot + 1, BeingSlot());
2979
2980
    // disabled for now, because it may broke replace/reorder sprites logic
2981
//    if (slot && mSlots[slot].spriteId == id)
2982
//        return;
2983
2984
    std::string color;
2985
2986
    // id = 0 means unequip
2987
    if (id == 0)
2988
    {
2989
        removeSprite(slot);
2990
        mSpriteDraw[slot] = 0;
2991
2992
        const int id1 = mSlots[slot].spriteId;
2993
        if (id1 != 0)
2994
            removeItemParticles(id1);
2995
    }
2996
    else
2997
    {
2998
        const ItemInfo &info = ItemDB::get(id);
2999
        const std::string &restrict filename = info.getSprite(
3000
            mGender, mSubType);
3001
        int lastTime = 0;
3002
        int startTime = 0;
3003
        AnimatedSprite *restrict equipmentSprite = nullptr;
3004
3005
        if (!filename.empty())
3006
        {
3007
            color = info.getDyeColorsString(colorId);
3008
            equipmentSprite = AnimatedSprite::delayedLoad(
3009
                pathJoin(paths.getStringValue("sprites"),
3010
                combineDye(filename, color)),
3011
                0);
3012
        }
3013
3014
        if (equipmentSprite != nullptr)
3015
        {
3016
            equipmentSprite->setSpriteDirection(getSpriteDirection());
3017
            startTime = getStartTime();
3018
            lastTime = getLastTime();
3019
        }
3020
3021
        CompoundSprite::setSprite(slot, equipmentSprite);
3022
        mSpriteDraw[slot] = id;
3023
3024
        addItemParticles(id, info.getDisplay());
3025
3026
        setAction(mAction, 0);
3027
        if (equipmentSprite != nullptr)
3028
        {
3029
            if (lastTime > 0)
3030
            {
3031
                equipmentSprite->setLastTime(startTime);
3032
                equipmentSprite->update(lastTime);
3033
            }
3034
        }
3035
    }
3036
3037
    BeingSlot &beingSlot = mSlots[slot];
3038
    beingSlot.spriteId = id;
3039
    beingSlot.color = STD_MOVE(color);
3040
    beingSlot.colorId = colorId;
3041
    beingSlot.cardsId = CardsList(nullptr);
3042
    recalcSpritesOrder();
3043
    if (beingEquipmentWindow != nullptr)
3044
        beingEquipmentWindow->updateBeing(this);
3045
}
3046
3047
// set sprite id, colors from cards, cards
3048
void Being::setSpriteCards(const unsigned int slot,
3049
                           const int id,
3050
                           const CardsList &cards) restrict2
3051
{
3052
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3053
        return;
3054
3055
    if (slot >= CAST_U32(mSprites.size()))
3056
        ensureSize(slot + 1);
3057
3058
    if (slot >= CAST_U32(mSlots.size()))
3059
        mSlots.resize(slot + 1, BeingSlot());
3060
3061
    // disabled for now, because it may broke replace/reorder sprites logic
3062
//    if (slot && mSlots[slot].spriteId == id)
3063
//        return;
3064
3065
    ItemColor colorId = ItemColor_one;
3066
    std::string color;
3067
3068
    // id = 0 means unequip
3069
    if (id == 0)
3070
    {
3071
        removeSprite(slot);
3072
        mSpriteDraw[slot] = 0;
3073
3074
        const int id1 = mSlots[slot].spriteId;
3075
        if (id1 != 0)
3076
            removeItemParticles(id1);
3077
    }
3078
    else
3079
    {
3080
        const ItemInfo &info = ItemDB::get(id);
3081
        const std::string &restrict filename = info.getSprite(
3082
            mGender, mSubType);
3083
        int lastTime = 0;
3084
        int startTime = 0;
3085
        AnimatedSprite *restrict equipmentSprite = nullptr;
3086
3087
        if (!cards.isEmpty())
3088
            colorId = ItemColorManager::getColorFromCards(cards);
3089
3090
        if (!filename.empty())
3091
        {
3092
            color = info.getDyeColorsString(colorId);
3093
3094
            equipmentSprite = AnimatedSprite::delayedLoad(
3095
                pathJoin(paths.getStringValue("sprites"),
3096
                combineDye(filename, color)),
3097
                0);
3098
        }
3099
3100
        if (equipmentSprite != nullptr)
3101
        {
3102
            equipmentSprite->setSpriteDirection(getSpriteDirection());
3103
            startTime = getStartTime();
3104
            lastTime = getLastTime();
3105
        }
3106
3107
        CompoundSprite::setSprite(slot, equipmentSprite);
3108
        mSpriteDraw[slot] = id;
3109
3110
        addItemParticlesCards(id,
3111
            info.getDisplay(),
3112
            cards);
3113
3114
        setAction(mAction, 0);
3115
        if (equipmentSprite != nullptr)
3116
        {
3117
            if (lastTime > 0)
3118
            {
3119
                equipmentSprite->setLastTime(startTime);
3120
                equipmentSprite->update(lastTime);
3121
            }
3122
        }
3123
    }
3124
3125
    BeingSlot &beingSlot = mSlots[slot];
3126
    beingSlot.spriteId = id;
3127
    beingSlot.color = STD_MOVE(color);
3128
    beingSlot.colorId = colorId;
3129
    beingSlot.cardsId = CardsList(cards);
3130
    recalcSpritesOrder();
3131
    if (beingEquipmentWindow != nullptr)
3132
        beingEquipmentWindow->updateBeing(this);
3133
}
3134
3135
void Being::setWeaponId(const int id) restrict2
3136
{
3137
    if (id == 0)
3138
        mEquippedWeapon = nullptr;
3139
    else
3140
        mEquippedWeapon = &ItemDB::get(id);
3141
}
3142
3143
void Being::setTempSprite(const unsigned int slot,
3144
                          const int id) restrict2
3145
{
3146
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3147
        return;
3148
3149
    if (slot >= CAST_U32(mSprites.size()))
3150
        ensureSize(slot + 1);
3151
3152
    if (slot >= CAST_U32(mSlots.size()))
3153
        mSlots.resize(slot + 1, BeingSlot());
3154
3155
    BeingSlot &beingSlot = mSlots[slot];
3156
3157
    // id = 0 means unequip
3158
    if (id == 0)
3159
    {
3160
        removeSprite(slot);
3161
        mSpriteDraw[slot] = 0;
3162
3163
        const int id1 = beingSlot.spriteId;
3164
        if (id1 != 0)
3165
            removeItemParticles(id1);
3166
    }
3167
    else
3168
    {
3169
        const ItemInfo &info = ItemDB::get(id);
3170
        const std::string &restrict filename = info.getSprite(
3171
            mGender, mSubType);
3172
        int lastTime = 0;
3173
        int startTime = 0;
3174
3175
        AnimatedSprite *restrict equipmentSprite = nullptr;
3176
3177
        if (!filename.empty())
3178
        {
3179
            ItemColor colorId = ItemColor_one;
3180
            const CardsList &cards = beingSlot.cardsId;
3181
            if (!cards.isEmpty())
3182
                colorId = ItemColorManager::getColorFromCards(cards);
3183
            std::string color = beingSlot.color;
3184
            if (color.empty())
3185
                color = info.getDyeColorsString(colorId);
3186
3187
            equipmentSprite = AnimatedSprite::delayedLoad(
3188
                pathJoin(paths.getStringValue("sprites"),
3189
                combineDye(filename, color)),
3190
                0);
3191
        }
3192
3193
        if (equipmentSprite != nullptr)
3194
        {
3195
            equipmentSprite->setSpriteDirection(getSpriteDirection());
3196
            startTime = getStartTime();
3197
            lastTime = getLastTime();
3198
        }
3199
3200
        CompoundSprite::setSprite(slot, equipmentSprite);
3201
        mSpriteDraw[slot] = id;
3202
3203
        // +++ here probably need use existing cards
3204
        addItemParticles(id, info.getDisplay());
3205
3206
        setAction(mAction, 0);
3207
        if (equipmentSprite != nullptr)
3208
        {
3209
            if (lastTime > 0)
3210
            {
3211
                equipmentSprite->setLastTime(startTime);
3212
                equipmentSprite->update(lastTime);
3213
            }
3214
        }
3215
    }
3216
}
3217
3218
void Being::setHairTempSprite(const unsigned int slot,
3219
                              const int id) restrict2
3220
{
3221
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3222
        return;
3223
3224
    if (slot >= CAST_U32(mSprites.size()))
3225
        ensureSize(slot + 1);
3226
3227
    if (slot >= CAST_U32(mSlots.size()))
3228
        mSlots.resize(slot + 1, BeingSlot());
3229
3230
    const CardsList &cards = mSlots[slot].cardsId;
3231
3232
    // id = 0 means unequip
3233
    if (id == 0)
3234
    {
3235
        removeSprite(slot);
3236
        mSpriteDraw[slot] = 0;
3237
3238
        const int id1 = mSlots[slot].spriteId;
3239
        if (id1 != 0)
3240
            removeItemParticles(id1);
3241
    }
3242
    else
3243
    {
3244
        const ItemInfo &info = ItemDB::get(id);
3245
        const std::string &restrict filename = info.getSprite(
3246
            mGender, mSubType);
3247
        int lastTime = 0;
3248
        int startTime = 0;
3249
        AnimatedSprite *restrict equipmentSprite = nullptr;
3250
3251
        if (!filename.empty())
3252
        {
3253
            ItemColor colorId = ItemColor_one;
3254
            if (!cards.isEmpty())
3255
                colorId = ItemColorManager::getColorFromCards(cards);
3256
3257
            std::string color = info.getDyeColorsString(mHairColor);
3258
            if (color.empty())
3259
                color = info.getDyeColorsString(colorId);
3260
3261
            equipmentSprite = AnimatedSprite::delayedLoad(
3262
                pathJoin(paths.getStringValue("sprites"),
3263
                combineDye(filename, color)),
3264
                0);
3265
        }
3266
3267
        if (equipmentSprite != nullptr)
3268
        {
3269
            equipmentSprite->setSpriteDirection(getSpriteDirection());
3270
            startTime = getStartTime();
3271
            lastTime = getLastTime();
3272
        }
3273
3274
        CompoundSprite::setSprite(slot, equipmentSprite);
3275
        mSpriteDraw[slot] = id;
3276
3277
        addItemParticles(id, info.getDisplay());
3278
3279
        setAction(mAction, 0);
3280
        if (equipmentSprite != nullptr)
3281
        {
3282
            if (lastTime > 0)
3283
            {
3284
                equipmentSprite->setLastTime(startTime);
3285
                equipmentSprite->update(lastTime);
3286
            }
3287
        }
3288
    }
3289
}
3290
3291
void Being::setHairColorSpriteID(const unsigned int slot,
3292
                                 const int id) restrict2
3293
{
3294
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3295
        return;
3296
3297
    if (slot >= CAST_U32(mSprites.size()))
3298
        ensureSize(slot + 1);
3299
3300
    if (slot >= CAST_U32(mSlots.size()))
3301
        mSlots.resize(slot + 1, BeingSlot());
3302
3303
    BeingSlot &beingSlot = mSlots[slot];
3304
    setSpriteColor(slot,
3305
        id,
3306
        beingSlot.color);
3307
}
3308
3309
void Being::setSpriteColor(const unsigned int slot,
3310
                           const std::string &restrict color) restrict2
3311
{
3312
    if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3313
        return;
3314
3315
    if (slot >= CAST_U32(mSprites.size()))
3316
        ensureSize(slot + 1);
3317
3318
    if (slot >= CAST_U32(mSlots.size()))
3319
        mSlots.resize(slot + 1, BeingSlot());
3320
3321
    // disabled for now, because it may broke replace/reorder sprites logic
3322
//    if (slot && mSlots[slot].spriteId == id)
3323
//        return;
3324
3325
    BeingSlot &beingSlot = mSlots[slot];
3326
    const int id = beingSlot.spriteId;
3327
3328
    // id = 0 means unequip
3329
    if (id != 0)
3330
    {
3331
        const ItemInfo &info = ItemDB::get(id);
3332
        const std::string &restrict filename = info.getSprite(
3333
            mGender, mSubType);
3334
        int lastTime = 0;
3335
        int startTime = 0;
3336
        AnimatedSprite *restrict equipmentSprite = nullptr;
3337
3338
        if (!filename.empty())
3339
        {
3340
            equipmentSprite = AnimatedSprite::delayedLoad(
3341
                pathJoin(paths.getStringValue("sprites"),
3342
                combineDye(filename, color)),
3343
                0);
3344
        }
3345
3346
        if (equipmentSprite != nullptr)
3347
        {
3348
            equipmentSprite->setSpriteDirection(getSpriteDirection());
3349
            startTime = getStartTime();
3350
            lastTime = getLastTime();
3351
        }
3352
3353
        CompoundSprite::setSprite(slot, equipmentSprite);
3354
3355
        setAction(mAction, 0);
3356
        if (equipmentSprite != nullptr)
3357
        {
3358
            if (lastTime > 0)
3359
            {
3360
                equipmentSprite->setLastTime(startTime);
3361
                equipmentSprite->update(lastTime);
3362
            }
3363
        }
3364
    }
3365
3366
    beingSlot.color = color;
3367
    beingSlot.colorId = ItemColor_one;
3368
    if (beingEquipmentWindow != nullptr)
3369
        beingEquipmentWindow->updateBeing(this);
3370
}
3371
3372
void Being::setHairStyle(const unsigned int slot,
3373
                         const int id) restrict2
3374
{
3375
    if (id != 0)
3376
    {
3377
        setSpriteColor(slot,
3378
            id,
3379
            ItemDB::get(id).getDyeColorsString(mHairColor));
3380
    }
3381
    else
3382
    {
3383
        setSpriteColor(slot,
3384
            0,
3385
            std::string());
3386
    }
3387
}
3388
3389
void Being::setHairColor(const unsigned int slot,
3390
                         const ItemColor color) restrict2
3391
{
3392
    mHairColor = color;
3393
    BeingSlot &beingSlot = mSlots[slot];
3394
    const int id = beingSlot.spriteId;
3395
    if (id != 0)
3396
    {
3397
        setSpriteColor(slot,
3398
            id,
3399
            ItemDB::get(id).getDyeColorsString(color));
3400
    }
3401
}
3402
3403
void Being::setSpriteSlot(const unsigned int slot,
3404
                          const BeingSlot &beingSlot)
3405
{
3406
    mSlots[slot] = beingSlot;
3407
}
3408
3409
void Being::dumpSprites() const restrict2
3410
{
3411
    STD_VECTOR<BeingSlot>::const_iterator it1 = mSlots.begin();
3412
    const STD_VECTOR<BeingSlot>::const_iterator it1_end = mSlots.end();
3413
3414
    logger->log("sprites");
3415
    for (; it1 != it1_end;
3416
         ++ it1)
3417
    {
3418
        logger->log("%d,%s,%d",
3419
            (*it1).spriteId,
3420
            (*it1).color.c_str(),
3421
            toInt((*it1).colorId, int));
3422
    }
3423
}
3424
3425
8
void Being::updateName() restrict2
3426
{
3427




8
    if (mShowName)
3428
4
        showName();
3429
8
}
3430
3431
206
void Being::reReadConfig()
3432
{
3433
    BLOCK_START("Being::reReadConfig")
3434
206
    if (mUpdateConfigTime + 1 < cur_time)
3435
    {
3436
        mAwayEffect = paths.getIntValue("afkEffectId");
3437
        mHighlightMapPortals = config.getBoolValue("highlightMapPortals");
3438
        mConfLineLim = config.getIntValue("chatMaxCharLimit");
3439
        mSpeechType = config.getIntValue("speech");
3440
        mHighlightMonsterAttackRange =
3441
            config.getBoolValue("highlightMonsterAttackRange");
3442
        mLowTraffic = config.getBoolValue("lowTraffic");
3443
        mDrawHotKeys = config.getBoolValue("drawHotKeys");
3444
        mShowBattleEvents = config.getBoolValue("showBattleEvents");
3445
        mShowMobHP = config.getBoolValue("showMobHP");
3446
        mShowOwnHP = config.getBoolValue("showOwnHP");
3447
        mShowGender = config.getBoolValue("showgender");
3448
        mShowLevel = config.getBoolValue("showlevel");
3449
        mShowPlayersStatus = config.getBoolValue("showPlayersStatus");
3450
        mEnableReorderSprites = config.getBoolValue("enableReorderSprites");
3451
        mHideErased = config.getBoolValue("hideErased");
3452
        mMoveNames = fromBool(config.getBoolValue("moveNames"), Move);
3453
        mUseDiagonal = config.getBoolValue("useDiagonalSpeed");
3454
        mShowBadges = static_cast<BadgeDrawType::Type>(
3455
            config.getIntValue("showBadges"));
3456
        mVisibleNamePos = static_cast<VisibleNamePos::Type>(
3457
            config.getIntValue("visiblenamespos"));
3458
3459
        mUpdateConfigTime = cur_time;
3460
    }
3461
    BLOCK_END("Being::reReadConfig")
3462
206
}
3463
3464
bool Being::updateFromCache() restrict2
3465
{
3466
    const BeingCacheEntry *restrict const entry =
3467
        Being::getCacheEntry(getId());
3468
3469
    if ((entry != nullptr) && entry->getTime() + 120 >= cur_time)
3470
    {
3471
        if (!entry->getName().empty())
3472
            setName(entry->getName());
3473
        setPartyName(entry->getPartyName());
3474
        setGuildName(entry->getGuildName());
3475
        setLevel(entry->getLevel());
3476
        setPvpRank(entry->getPvpRank());
3477
        setIp(entry->getIp());
3478
        setTeamId(entry->getTeamId());
3479
3480
        mAdvanced = entry->isAdvanced();
3481
        if (mAdvanced)
3482
        {
3483
            const int flags = entry->getFlags();
3484
            if ((serverFeatures != nullptr) &&
3485
                Net::getNetworkType() == ServerType::TMWATHENA)
3486
            {
3487
                mShop = ((flags & BeingFlag::SHOP) != 0);
3488
            }
3489
            mAway = ((flags & BeingFlag::AWAY) != 0);
3490
            mInactive = ((flags & BeingFlag::INACTIVE) != 0);
3491
            if (mShop || mAway || mInactive)
3492
                updateName();
3493
        }
3494
        else
3495
        {
3496
            if (Net::getNetworkType() == ServerType::TMWATHENA)
3497
                mShop = false;
3498
            mAway = false;
3499
            mInactive = false;
3500
        }
3501
3502
        showShopBadge(mShop);
3503
        showInactiveBadge(mInactive);
3504
        showAwayBadge(mAway);
3505
        updateAwayEffect();
3506
        if (mType == ActorType::Player || (mTeamId != 0u))
3507
            updateColors();
3508
        return true;
3509
    }
3510
    return false;
3511
}
3512
3513
void Being::addToCache() const restrict2
3514
{
3515
    if (localPlayer == this)
3516
        return;
3517
3518
    BeingCacheEntry *entry = Being::getCacheEntry(getId());
3519
    if (entry == nullptr)
3520
    {
3521
        entry = new BeingCacheEntry(getId());
3522
        beingInfoCache.push_front(entry);
3523
3524
        if (beingInfoCache.size() >= CACHE_SIZE)
3525
        {
3526
            delete beingInfoCache.back();
3527
            beingInfoCache.pop_back();
3528
        }
3529
    }
3530
    if (!mLowTraffic)
3531
        return;
3532
3533
    entry->setName(mName);
3534
    entry->setLevel(getLevel());
3535
    entry->setPartyName(getPartyName());
3536
    entry->setGuildName(getGuildName());
3537
    entry->setTime(cur_time);
3538
    entry->setPvpRank(getPvpRank());
3539
    entry->setIp(getIp());
3540
    entry->setAdvanced(isAdvanced());
3541
    entry->setTeamId(getTeamId());
3542
    if (isAdvanced())
3543
    {
3544
        int flags = 0;
3545
        if (Net::getNetworkType() == ServerType::TMWATHENA && mShop)
3546
            flags += BeingFlag::SHOP;
3547
        if (mAway)
3548
            flags += BeingFlag::AWAY;
3549
        if (mInactive)
3550
            flags += BeingFlag::INACTIVE;
3551
        entry->setFlags(flags);
3552
    }
3553
    else
3554
    {
3555
        entry->setFlags(0);
3556
    }
3557
}
3558
3559
BeingCacheEntry* Being::getCacheEntry(const BeingId id)
3560
{
3561
    FOR_EACH (std::list<BeingCacheEntry*>::iterator, i, beingInfoCache)
3562
    {
3563
        if (*i == nullptr)
3564
            continue;
3565
3566
        if (id == (*i)->getId())
3567
        {
3568
            // Raise priority: move it to front
3569
            if ((*i)->getTime() + 120 < cur_time)
3570
            {
3571
                beingInfoCache.splice(beingInfoCache.begin(),
3572
                                      beingInfoCache, i);
3573
            }
3574
            return *i;
3575
        }
3576
    }
3577
    return nullptr;
3578
}
3579
3580
3581
void Being::setGender(const GenderT gender) restrict2
3582
{
3583
    if (charServerHandler == nullptr)
3584
        return;
3585
3586
    if (gender != mGender)
3587
    {
3588
        mGender = gender;
3589
3590
        const unsigned int sz = CAST_U32(mSlots.size());
3591
3592
        if (sz > CAST_U32(mSprites.size()))
3593
            ensureSize(sz);
3594
3595
        // Reload all subsprites
3596
        for (unsigned int i = 0;
3597
             i < sz;
3598
             i++)
3599
        {
3600
            BeingSlot &beingSlot = mSlots[i];
3601
            const int id = beingSlot.spriteId;
3602
            if (id != 0)
3603
            {
3604
                const ItemInfo &info = ItemDB::get(id);
3605
                const std::string &restrict filename = info.getSprite(
3606
                    mGender, mSubType);
3607
                int lastTime = 0;
3608
                int startTime = 0;
3609
                AnimatedSprite *restrict equipmentSprite = nullptr;
3610
3611
                if (!filename.empty())
3612
                {
3613
                    equipmentSprite = AnimatedSprite::delayedLoad(
3614
                        pathJoin(paths.getStringValue("sprites"),
3615
                        combineDye(filename, beingSlot.color)),
3616
                        0);
3617
                }
3618
3619
                if (equipmentSprite != nullptr)
3620
                {
3621
                    equipmentSprite->setSpriteDirection(getSpriteDirection());
3622
                    startTime = getStartTime();
3623
                    lastTime = getLastTime();
3624
                }
3625
3626
                CompoundSprite::setSprite(i, equipmentSprite);
3627
                setAction(mAction, 0);
3628
                if (equipmentSprite != nullptr)
3629
                {
3630
                    if (lastTime > 0)
3631
                    {
3632
                        equipmentSprite->setLastTime(startTime);
3633
                        equipmentSprite->update(lastTime);
3634
                    }
3635
                }
3636
3637
                if (beingEquipmentWindow != nullptr)
3638
                    beingEquipmentWindow->updateBeing(this);
3639
            }
3640
        }
3641
3642
        updateName();
3643
    }
3644
}
3645
3646
void Being::showGmBadge(const bool show) restrict2
3647
{
3648
    delete2(mBadges[BadgeIndex::Gm]);
3649
    if (show &&
3650
        mIsGM &&
3651
        mShowBadges != BadgeDrawType::Hide &&
3652
        GroupDb::getShowBadge(mGroupId))
3653
    {
3654
        const std::string &gmBadge = GroupDb::getBadge(mGroupId);
3655
        if (!gmBadge.empty())
3656
        {
3657
            mBadges[BadgeIndex::Gm] = AnimatedSprite::load(
3658
                paths.getStringValue("badges") + gmBadge,
3659
                0);
3660
        }
3661
    }
3662
    updateBadgesCount();
3663
    updateBadgesPosition();
3664
}
3665
3666
void Being::setGM(const bool gm) restrict2
3667
{
3668
    if (mIsGM != gm)
3669
    {
3670
        mIsGM = gm;
3671
        updateColors();
3672
    }
3673
}
3674
3675
void Being::talkTo() const restrict2
3676
{
3677
    if (npcHandler == nullptr)
3678
        return;
3679
3680
    if (!PacketLimiter::limitPackets(PacketType::PACKET_NPC_TALK))
3681
    {
3682
        // using workaround...
3683
        if ((playerHandler != nullptr) &&
3684
            PacketLimiter::limitPackets(PacketType::PACKET_ATTACK))
3685
        {
3686
            playerHandler->attack(mId, Keep_false);
3687
        }
3688
        return;
3689
    }
3690
3691
    npcHandler->talk(this);
3692
}
3693
3694
void Being::drawPlayer(Graphics *restrict const graphics,
3695
                       const int offsetX,
3696
                       const int offsetY) const restrict2
3697
{
3698
    if (!mErased)
3699
    {
3700
        // getActorX() + offsetX;
3701
        const int px = mPixelX - mapTileSize / 2 + offsetX;
3702
        // getActorY() + offsetY;
3703
        const int py = mPixelY - mapTileSize + offsetY;
3704
        if (mHorseInfo != nullptr)
3705
        {
3706
            HorseOffset &offset = mHorseInfo->offsets[mSpriteDirection];
3707
            for_each_horses(mDownHorseSprites)
3708
            {
3709
                (*it)->draw(graphics,
3710
                    px + offset.downOffsetX,
3711
                    py + offset.downOffsetY);
3712
            }
3713
3714
            drawBeingCursor(graphics, px, py);
3715
            drawPlayerSpriteAt(graphics,
3716
                px + offset.riderOffsetX,
3717
                py + offset.riderOffsetY);
3718
3719
            for_each_horses(mUpHorseSprites)
3720
            {
3721
                (*it)->draw(graphics,
3722
                    px + offset.upOffsetX,
3723
                    py + offset.upOffsetY);
3724
            }
3725
        }
3726
        else
3727
        {
3728
            drawBeingCursor(graphics, px, py);
3729
            drawPlayerSpriteAt(graphics, px, py);
3730
        }
3731
    }
3732
}
3733
3734
void Being::drawBeingCursor(Graphics *const graphics,
3735
                            const int offsetX,
3736
                            const int offsetY) const
3737
{
3738
    if (mUsedTargetCursor != nullptr)
3739
    {
3740
        mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK);
3741
        if (mInfo == nullptr)
3742
        {
3743
            mUsedTargetCursor->draw(graphics,
3744
                offsetX - mCursorPaddingX,
3745
                offsetY - mCursorPaddingY);
3746
        }
3747
        else
3748
        {
3749
            mUsedTargetCursor->draw(graphics,
3750
                offsetX + mInfo->getTargetOffsetX() - mCursorPaddingX,
3751
                offsetY + mInfo->getTargetOffsetY() - mCursorPaddingY);
3752
        }
3753
    }
3754
}
3755
3756
void Being::drawOther(Graphics *restrict const graphics,
3757
                      const int offsetX,
3758
                      const int offsetY) const restrict2
3759
{
3760
    // getActorX() + offsetX;
3761
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3762
    // getActorY() + offsetY;
3763
    const int py = mPixelY - mapTileSize + offsetY;
3764
    drawBeingCursor(graphics, px, py);
3765
    drawOtherSpriteAt(graphics, px, py);
3766
}
3767
3768
void Being::drawNpc(Graphics *restrict const graphics,
3769
                    const int offsetX,
3770
                    const int offsetY) const restrict2
3771
{
3772
    // getActorX() + offsetX;
3773
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3774
    // getActorY() + offsetY;
3775
    const int py = mPixelY - mapTileSize + offsetY;
3776
    drawBeingCursor(graphics, px, py);
3777
    drawNpcSpriteAt(graphics, px, py);
3778
}
3779
3780
void Being::drawMonster(Graphics *restrict const graphics,
3781
                        const int offsetX,
3782
                        const int offsetY) const restrict2
3783
{
3784
    // getActorX() + offsetX;
3785
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3786
    // getActorY() + offsetY;
3787
    const int py = mPixelY - mapTileSize + offsetY;
3788
    drawBeingCursor(graphics, px, py);
3789
    drawMonsterSpriteAt(graphics, px, py);
3790
}
3791
3792
void Being::drawHomunculus(Graphics *restrict const graphics,
3793
                           const int offsetX,
3794
                           const int offsetY) const restrict2
3795
{
3796
    // getActorX() + offsetX;
3797
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3798
    // getActorY() + offsetY;
3799
    const int py = mPixelY - mapTileSize + offsetY;
3800
    drawBeingCursor(graphics, px, py);
3801
    drawHomunculusSpriteAt(graphics, px, py);
3802
}
3803
3804
void Being::drawMercenary(Graphics *restrict const graphics,
3805
                          const int offsetX,
3806
                          const int offsetY) const restrict2
3807
{
3808
    // getActorX() + offsetX;
3809
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3810
    // getActorY() + offsetY;
3811
    const int py = mPixelY - mapTileSize + offsetY;
3812
    drawBeingCursor(graphics, px, py);
3813
    drawMercenarySpriteAt(graphics, px, py);
3814
}
3815
3816
void Being::drawElemental(Graphics *restrict const graphics,
3817
                          const int offsetX,
3818
                          const int offsetY) const restrict2
3819
{
3820
    // getActorX() + offsetX;
3821
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3822
    // getActorY() + offsetY;
3823
    const int py = mPixelY - mapTileSize + offsetY;
3824
    drawBeingCursor(graphics, px, py);
3825
    drawElementalSpriteAt(graphics, px, py);
3826
}
3827
3828
void Being::drawPortal(Graphics *restrict const graphics,
3829
                       const int offsetX,
3830
                       const int offsetY) const restrict2
3831
{
3832
    // getActorX() + offsetX;
3833
    const int px = mPixelX - mapTileSize / 2 + offsetX;
3834
    // getActorY() + offsetY;
3835
    const int py = mPixelY - mapTileSize + offsetY;
3836
    drawPortalSpriteAt(graphics, px, py);
3837
}
3838
3839
void Being::draw(Graphics *restrict const graphics,
3840
                 const int offsetX,
3841
                 const int offsetY) const restrict2
3842
{
3843
    switch (mType)
3844
    {
3845
        case ActorType::Player:
3846
            drawPlayer(graphics,
3847
                offsetX,
3848
                offsetY);
3849
            break;
3850
        case ActorType::Portal:
3851
            drawPortal(graphics,
3852
                offsetX,
3853
                offsetY);
3854
            break;
3855
        case ActorType::Homunculus:
3856
            drawHomunculus(graphics,
3857
                offsetX,
3858
                offsetY);
3859
            break;
3860
        case ActorType::Mercenary:
3861
            drawMercenary(graphics,
3862
                offsetX,
3863
                offsetY);
3864
            break;
3865
        case ActorType::Elemental:
3866
            drawElemental(graphics,
3867
                offsetX,
3868
                offsetY);
3869
            break;
3870
        case ActorType::Monster:
3871
            drawMonster(graphics,
3872
                offsetX,
3873
                offsetY);
3874
            break;
3875
        case ActorType::Npc:
3876
            drawNpc(graphics,
3877
                offsetX,
3878
                offsetY);
3879
            break;
3880
        case ActorType::Pet:
3881
        case ActorType::SkillUnit:
3882
        case ActorType::Unknown:
3883
        case ActorType::FloorItem:
3884
        case ActorType::Avatar:
3885
        default:
3886
            drawOther(graphics,
3887
                offsetX,
3888
                offsetY);
3889
            break;
3890
    }
3891
}
3892
3893
void Being::drawPlayerSprites(Graphics *restrict const graphics,
3894
                              const int posX,
3895
                              const int posY) const restrict2
3896
{
3897
    const int sz = CompoundSprite::getNumberOfLayers();
3898
    for (int f = 0; f < sz; f ++)
3899
    {
3900
        const int rSprite = mSpriteHide[mSpriteRemap[f]];
3901
        if (rSprite == 1)
3902
            continue;
3903
3904
        Sprite *restrict const sprite = mSprites[mSpriteRemap[f]];
3905
        if (sprite != nullptr)
3906
        {
3907
            sprite->setAlpha(mAlpha);
3908
            sprite->draw(graphics, posX, posY);
3909
        }
3910
    }
3911
}
3912
3913
void Being::drawSpritesSDL(Graphics *restrict const graphics,
3914
                           const int posX,
3915
                           const int posY) const restrict2
3916
{
3917
    const size_t sz = mSprites.size();
3918
    for (size_t f = 0; f < sz; f ++)
3919
    {
3920
        const int rSprite = mSpriteHide[mSpriteRemap[f]];
3921
        if (rSprite == 1)
3922
            continue;
3923
3924
        const Sprite *restrict const sprite = mSprites[mSpriteRemap[f]];
3925
        if (sprite != nullptr)
3926
            sprite->draw(graphics, posX, posY);
3927
    }
3928
}
3929
3930
void Being::drawBasic(Graphics *restrict const graphics,
3931
                      const int x,
3932
                      const int y) const restrict2
3933
{
3934
    drawCompound(graphics, x, y);
3935
}
3936
3937
void Being::drawCompound(Graphics *const graphics,
3938
                         const int posX,
3939
                         const int posY) const
3940
{
3941
    FUNC_BLOCK("CompoundSprite::draw", 1)
3942
    if (mNeedsRedraw)
3943
        updateImages();
3944
3945
    if (mSprites.empty())  // Nothing to draw
3946
        return;
3947
3948
    if (mAlpha == 1.0F && (mImage != nullptr))
3949
    {
3950
        graphics->drawImage(mImage,
3951
            posX + mOffsetX,
3952
            posY + mOffsetY);
3953
    }
3954
    else if ((mAlpha != 0.0f) && (mAlphaImage != nullptr))
3955
    {
3956
        mAlphaImage->setAlpha(mAlpha);
3957
        graphics->drawImage(mAlphaImage,
3958
            posX + mOffsetX,
3959
            posY + mOffsetY);
3960
    }
3961
    else
3962
    {
3963
        Being::drawPlayerSprites(graphics, posX, posY);
3964
    }
3965
}
3966
3967
void Being::drawPlayerSpriteAt(Graphics *restrict const graphics,
3968
                               const int x,
3969
                               const int y) const restrict2
3970
{
3971
    drawCompound(graphics, x, y);
3972
3973
    if (mShowOwnHP &&
3974
        (mInfo != nullptr) &&
3975
        localPlayer == this &&
3976
        mAction != BeingAction::DEAD)
3977
    {
3978
        drawHpBar(graphics,
3979
            PlayerInfo::getAttribute(Attributes::PLAYER_MAX_HP),
3980
            PlayerInfo::getAttribute(Attributes::PLAYER_HP),
3981
            0,
3982
            UserColorId::PLAYER_HP,
3983
            UserColorId::PLAYER_HP2,
3984
            x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
3985
            y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
3986
            2 * 50,
3987
            4);
3988
    }
3989
}
3990
3991
void Being::drawOtherSpriteAt(Graphics *restrict const graphics,
3992
                              const int x,
3993
                              const int y) const restrict2
3994
{
3995
    CompoundSprite::drawSimple(graphics, x, y);
3996
}
3997
3998
void Being::drawNpcSpriteAt(Graphics *restrict const graphics,
3999
                            const int x,
4000
                            const int y) const restrict2
4001
{
4002
    drawCompound(graphics, x, y);
4003
}
4004
4005
void Being::drawMonsterSpriteAt(Graphics *restrict const graphics,
4006
                                const int x,
4007
                                const int y) const restrict2
4008
{
4009
    if (mHighlightMonsterAttackRange &&
4010
        mType == ActorType::Monster &&
4011
        mAction != BeingAction::DEAD)
4012
    {
4013
        if (userPalette == nullptr)
4014
        {
4015
            CompoundSprite::drawSimple(graphics, x, y);
4016
            return;
4017
        }
4018
4019
        int attackRange;
4020
        if (mAttackRange != 0)
4021
            attackRange = mapTileSize * mAttackRange;
4022
        else
4023
            attackRange = mapTileSize;
4024
4025
        graphics->setColor(userPalette->getColorWithAlpha(
4026
            UserColorId::MONSTER_ATTACK_RANGE));
4027
4028
        graphics->fillRectangle(Rect(
4029
            x - attackRange, y - attackRange,
4030
            2 * attackRange + mapTileSize, 2 * attackRange + mapTileSize));
4031
    }
4032
4033
    CompoundSprite::drawSimple(graphics, x, y);
4034
4035
    if (mShowMobHP &&
4036
        (mInfo != nullptr) &&
4037
        (localPlayer != nullptr) &&
4038
        localPlayer->getTarget() == this &&
4039
        mType == ActorType::Monster)
4040
    {
4041
        // show hp bar here
4042
        int maxHP = mMaxHP;
4043
        if (maxHP == 0)
4044
            maxHP = mInfo->getMaxHP();
4045
4046
        drawHpBar(graphics,
4047
                  maxHP,
4048
                  mHP,
4049
                  mDamageTaken,
4050
                  UserColorId::MONSTER_HP,
4051
                  UserColorId::MONSTER_HP2,
4052
                  x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4053
                  y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4054
                  2 * 50,
4055
                  4);
4056
    }
4057
}
4058
4059
void Being::drawHomunculusSpriteAt(Graphics *restrict const graphics,
4060
                                   const int x,
4061
                                   const int y) const restrict2
4062
{
4063
    if (mHighlightMonsterAttackRange &&
4064
        mAction != BeingAction::DEAD)
4065
    {
4066
        if (userPalette == nullptr)
4067
        {
4068
            CompoundSprite::drawSimple(graphics, x, y);
4069
            return;
4070
        }
4071
4072
        int attackRange;
4073
        if (mAttackRange != 0)
4074
            attackRange = mapTileSize * mAttackRange;
4075
        else
4076
            attackRange = mapTileSize;
4077
4078
        graphics->setColor(userPalette->getColorWithAlpha(
4079
            UserColorId::MONSTER_ATTACK_RANGE));
4080
4081
        graphics->fillRectangle(Rect(
4082
            x - attackRange, y - attackRange,
4083
            2 * attackRange + mapTileSize, 2 * attackRange + mapTileSize));
4084
    }
4085
4086
    CompoundSprite::drawSimple(graphics, x, y);
4087
4088
    if (mShowMobHP &&
4089
        (mInfo != nullptr))
4090
    {
4091
        const HomunculusInfo *const info = PlayerInfo::getHomunculus();
4092
        if ((info != nullptr) &&
4093
            mId == info->id)
4094
        {
4095
            // show hp bar here
4096
            int maxHP = PlayerInfo::getStatBase(Attributes::HOMUN_MAX_HP);
4097
            if (maxHP == 0)
4098
                maxHP = mInfo->getMaxHP();
4099
4100
            drawHpBar(graphics,
4101
                maxHP,
4102
                PlayerInfo::getStatBase(Attributes::HOMUN_HP),
4103
                mDamageTaken,
4104
                UserColorId::HOMUN_HP,
4105
                UserColorId::HOMUN_HP2,
4106
                x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4107
                y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4108
                2 * 50,
4109
                4);
4110
        }
4111
    }
4112
}
4113
4114
void Being::drawMercenarySpriteAt(Graphics *restrict const graphics,
4115
                                  const int x,
4116
                                  const int y) const restrict2
4117
{
4118
    if (mHighlightMonsterAttackRange &&
4119
        mAction != BeingAction::DEAD)
4120
    {
4121
        if (userPalette ==