GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/being.cpp Lines: 203 2617 7.8 %
Date: 2021-03-17 Branches: 145 2842 5.1 %

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

105
    mWalkSpeed(playerHandler != nullptr ?
211
2
               playerHandler->getDefaultWalkSpeed() : 1),
212

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

103
    switch (mType)
298
    {
299
        case ActorType::Player:
300
        case ActorType::Mercenary:
301
        case ActorType::Pet:
302
        case ActorType::Homunculus:
303
        case ActorType::Elemental:
304
101
            showName1 = static_cast<VisibleName::Type>(
305
404
                config.getIntValue("visiblenames"));
306
101
            break;
307
        case ActorType::Portal:
308
        case ActorType::SkillUnit:
309
            showName1 = VisibleName::Hide;
310
            break;
311
        default:
312
        case ActorType::Unknown:
313
        case ActorType::Npc:
314
        case ActorType::Monster:
315
        case ActorType::FloorItem:
316
        case ActorType::Avatar:
317
            break;
318
    }
319
320
103
    if (mType != ActorType::Npc)
321
103
        mGotComment = true;
322
323
412
    config.addListener("visiblenames", this);
324
325
103
    reReadConfig();
326
327

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

12
    if (show &&
1123

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


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

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

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

17
        if (mType == ActorType::Monster ||
2514
8
            mVisibleNamePos == VisibleNamePos::Top)
2515
        {
2516
2
            offsetY += - settings.playerNameOffset - mDispName->getHeight();
2517
        }
2518
9
        mDispName->adviseXY(offsetX, offsetY, mMoveNames);
2519
    }
2520
9
    updateBadgesPosition();
2521
9
}
2522
2523
void Being::optionChanged(const std::string &restrict value) restrict2
2524
{
2525
    if (mType == ActorType::Player && value == "visiblenames")
2526
    {
2527
        setShowName(config.getIntValue("visiblenames") == VisibleName::Show);
2528
        updateBadgesPosition();
2529
    }
2530
}
2531
2532
void Being::flashName(const int time) restrict2
2533
{
2534
    if (mDispName != nullptr)
2535
        mDispName->flash(time);
2536
}
2537
2538
std::string Being::getGenderSignWithSpace() const restrict2
2539
{
2540
    const std::string &restrict str = getGenderSign();
2541
    if (str.empty())
2542
        return str;
2543
    else
2544
        return std::string(" ").append(str);
2545
}
2546
2547
std::string Being::getGenderSign() const restrict2
2548
{
2549
    std::string str;
2550
    if (mShowGender)
2551
    {
2552
        if (getGender() == Gender::FEMALE)
2553
            str = "\u2640";
2554
        else if (getGender() == Gender::MALE)
2555
            str = "\u2642";
2556
        else if (mType == ActorType::Player)
2557
            str = "\u2640";
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
110
void Being::showName() restrict2
2579
{
2580
220
    if (mName.empty())
2581
101
        return;
2582
2583
18
    delete2(mDispName)
2584
2585

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

9
    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
9
    if (mType == ActorType::Monster)
2600
    {
2601

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

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

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

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


8
             && !playerRelations.isGoodName(this) && (gui != nullptr))
2613
    {
2614
        font = gui->getSecureFont();
2615
    }
2616
2617
9
    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
9
        mDispName = new FlashText(displayName,
2629
            mPixelX,
2630
            mPixelY,
2631
            Graphics::CENTER,
2632
            mNameColor,
2633

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

71
    switch (mTeamId)
2642
    {
2643
        case 0:
2644
        default:
2645
71
            mNameColor = &userPalette->getColor(defaultColor,
2646
71
                255U);
2647
71
            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
71
}
2662
2663
109
void Being::updateColors()
2664
{
2665
109
    if (userPalette != nullptr)
2666
    {
2667
71
        if (mType == ActorType::Monster)
2668
        {
2669
            setDefaultNameColor(UserColorId::MONSTER);
2670
            mTextColor = &userPalette->getColor(UserColorId::MONSTER,
2671
                255U);
2672
        }
2673
71
        else if (mType == ActorType::Npc)
2674
        {
2675
            setDefaultNameColor(UserColorId::NPC);
2676
            mTextColor = &userPalette->getColor(UserColorId::NPC,
2677
                255U);
2678
        }
2679
71
        else if (mType == ActorType::Pet)
2680
        {
2681
            setDefaultNameColor(UserColorId::PET);
2682
            mTextColor = &userPalette->getColor(UserColorId::PET,
2683
                255U);
2684
        }
2685
71
        else if (mType == ActorType::Homunculus)
2686
        {
2687
            setDefaultNameColor(UserColorId::HOMUNCULUS);
2688
            mTextColor = &userPalette->getColor(UserColorId::HOMUNCULUS,
2689
                255U);
2690
        }
2691
71
        else if (mType == ActorType::SkillUnit)
2692
        {
2693
            setDefaultNameColor(UserColorId::SKILLUNIT);
2694
            mTextColor = &userPalette->getColor(UserColorId::SKILLUNIT,
2695
                255U);
2696
        }
2697
71
        else if (this == localPlayer)
2698
        {
2699
            mNameColor = &userPalette->getColor(UserColorId::SELF, 255U);
2700
            mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2701
        }
2702
        else
2703
        {
2704
71
            mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2705
2706
71
            if (playerRelations.getRelation(mName) != Relation::ERASED)
2707
71
                mErased = false;
2708
            else
2709
                mErased = true;
2710
2711
71
            if (mIsGM)
2712
            {
2713
                mTextColor = &userPalette->getColor(UserColorId::GM,
2714
                    255U);
2715
                mNameColor = &userPalette->getColor(UserColorId::GM,
2716
                    255U);
2717
            }
2718
71
            else if (mEnemy)
2719
            {
2720
                mNameColor = &userPalette->getColor(UserColorId::ENEMY,
2721
                255U);
2722
            }
2723

142
            else if ((mParty != nullptr) && (localPlayer != nullptr)
2724

71
                     && mParty == localPlayer->getParty())
2725
            {
2726
                mNameColor = &userPalette->getColor(UserColorId::PARTY,
2727
                    255U);
2728
            }
2729

144
            else if ((localPlayer != nullptr) && (getGuild() != nullptr)
2730

71
                     && getGuild() == localPlayer->getGuild())
2731
            {
2732
                mNameColor = &userPalette->getColor(UserColorId::GUILD,
2733
                    255U);
2734
            }
2735
71
            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

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

142
                     == Relation::IGNORED ||
2750
71
                     playerRelations.getRelation(mName) == Relation::ENEMY2)
2751
            {
2752
                mNameColor = &userPalette->getColor(UserColorId::IGNORED,
2753
                    255U);
2754
            }
2755
71
            else if (playerRelations.getRelation(mName) == Relation::ERASED)
2756
            {
2757
                mNameColor = &userPalette->getColor(UserColorId::ERASED,
2758
                    255U);
2759
            }
2760
            else
2761
            {
2762
71
                setDefaultNameColor(UserColorId::PC);
2763
            }
2764
        }
2765
2766
71
        if (mDispName != nullptr)
2767
            mDispName->setColor(mNameColor);
2768
    }
2769
109
}
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
4
void Being::updateName() restrict2
3426
{
3427




4
    if (mShowName)
3428
2
        showName();
3429
4
}
3430
3431
103
void Being::reReadConfig()
3432
{
3433
    BLOCK_START("Being::reReadConfig")
3434
103
    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
103
}
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 == nullptr)
4122
</