GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/being.cpp Lines: 203 2620 7.7 %
Date: 2017-11-29 Branches: 137 2765 5.0 %

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

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

208
    mSpeed(playerHandler != nullptr ?
213
4
           playerHandler->getDefaultWalkSpeed() : 1),
214
    mIp(),
215
204
    mSpriteRemap(new int[20]),
216
204
    mSpriteHide(new int[20]),
217
204
    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
5508
    mAllowNpcEquipment(false)
277
{
278
4284
    for (int f = 0; f < 20; f ++)
279
    {
280
4080
        mSpriteRemap[f] = f;
281
4080
        mSpriteHide[f] = 0;
282
4080
        mSpriteDraw[f] = 0;
283
    }
284
285
3876
    for_each_badges()
286
1836
        mBadges[f] = nullptr;
287
204
}
288
289
204
void Being::postInit(const BeingTypeId subtype,
290
                     Map *const map)
291
{
292
204
    setMap(map);
293
204
    setSubtype(subtype, 0);
294
295
204
    VisibleName::Type showName1 = VisibleName::Hide;
296
297

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

800
                config.getIntValue("visiblenames"));
306
200
            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
204
    if (mType != ActorType::Npc)
321
204
        mGotComment = true;
322
323

816
    config.addListener("visiblenames", this);
324
325
204
    reReadConfig();
326
327

204
    if (mType == ActorType::Npc ||
328
        showName1 == VisibleName::Show)
329
    {
330
200
        setShowName(true);
331
    }
332
333
204
    updateColors();
334
204
    updatePercentHP();
335
204
}
336
337
4098
Being::~Being()
338
{
339
816
    config.removeListener("visiblenames", this);
340
    CHECKLISTENERS
341
342
204
    delete [] mSpriteRemap;
343
204
    mSpriteRemap = nullptr;
344
204
    delete [] mSpriteHide;
345
204
    mSpriteHide = nullptr;
346
204
    delete [] mSpriteDraw;
347
204
    mSpriteDraw = nullptr;
348
349
2040
    for_each_badges()
350
1836
        delete2(mBadges[f]);
351
352
408
    delete2(mSpeechBubble);
353
408
    delete2(mDispName);
354
204
    delete2(mText);
355
204
    delete2(mEmotionSprite);
356
204
    delete2(mAnimationEffect);
357
204
    delete2(mCastingEffect);
358
204
    mBadgesCount = 0;
359
204
    delete2(mChat);
360
204
    removeHorse();
361
362
204
    removeAllItemsParticles();
363
408
    mSpiritParticles.clear();
364
222
}
365
366
void Being::createSpeechBubble() restrict2
367
{
368
    CREATEWIDGETV0(mSpeechBubble, SpeechBubble);
369
}
370
371
204
void Being::setSubtype(const BeingTypeId subtype,
372
                       const uint16_t look) restrict2
373
{
374
204
    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, mY, dstX, dstY, getBlockWalkMask()));
547
}
548
549
void Being::clearPath() restrict2
550
{
551
    mPath.clear();
552
}
553
554
void Being::setPath(const Path &restrict path) restrict2
555
{
556
    mPath = path;
557
    if (mPath.empty())
558
        return;
559
560
    if (mAction != BeingAction::MOVE && mAction != BeingAction::DEAD)
561
    {
562
        nextTile();
563
        mActionTime = tick_time;
564
    }
565
}
566
567
void Being::setSpeech(const std::string &restrict text,
568
                      const std::string &restrict channel,
569
                      int time) restrict2
570
{
571
    if (userPalette == nullptr)
572
        return;
573
574
    if (!channel.empty() &&
575
        ((langChatTab == nullptr) || langChatTab->getChannelName() != channel))
576
    {
577
        return;
578
    }
579
580
    // Remove colors
581
    mSpeech = removeColors(text);
582
583
    // Trim whitespace
584
    trim(mSpeech);
585
586
    const unsigned int lineLim = mConfLineLim;
587
    if (lineLim > 0 && mSpeech.length() > lineLim)
588
        mSpeech = mSpeech.substr(0, lineLim);
589
590
    trim(mSpeech);
591
    if (mSpeech.empty())
592
        return;
593
594
    if (time == 0)
595
    {
596
        const size_t sz = mSpeech.size();
597
        if (sz < 200)
598
            time = CAST_S32(SPEECH_TIME - 300 + (3 * sz));
599
    }
600
601
    if (time < CAST_S32(SPEECH_MIN_TIME))
602
        time = CAST_S32(SPEECH_MIN_TIME);
603
604
    // Check for links
605
    size_t start = mSpeech.find('[');
606
    size_t e = mSpeech.find(']', start);
607
608
    while (start != std::string::npos && e != std::string::npos)
609
    {
610
        // Catch multiple embeds and ignore them so it doesn't crash the client.
611
        while ((mSpeech.find('[', start + 1) != std::string::npos) &&
612
               (mSpeech.find('[', start + 1) < e))
613
        {
614
            start = mSpeech.find('[', start + 1);
615
        }
616
617
        size_t position = mSpeech.find('|');
618
        if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
619
        {
620
            mSpeech.erase(e, 1);
621
            mSpeech.erase(start, (position - start) + 1);
622
        }
623
        position = mSpeech.find("@@");
624
625
        while (position != std::string::npos)
626
        {
627
            mSpeech.erase(position, 2);
628
            position = mSpeech.find('@');
629
        }
630
631
        start = mSpeech.find('[', start + 1);
632
        e = mSpeech.find(']', start);
633
    }
634
635
    if (!mSpeech.empty())
636
    {
637
        mSpeechTime = time <= CAST_S32(SPEECH_MAX_TIME)
638
            ? time : CAST_S32(SPEECH_MAX_TIME);
639
    }
640
641
    const int speech = mSpeechType;
642
    if (speech == BeingSpeech::TEXT_OVERHEAD)
643
    {
644
        delete mText;
645
        mText = nullptr;
646
        mText = new Text(mSpeech,
647
            mPixelX,
648
            mPixelY - getHeight(),
649
            Graphics::CENTER,
650
            &userPalette->getColor(UserColorId::PARTICLE),
651
            Speech_true);
652
        mText->adviseXY(mPixelX,
653
            (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
654
            mMoveNames);
655
    }
656
    else
657
    {
658
        const bool isShowName = (speech == BeingSpeech::NAME_IN_BUBBLE);
659
        if (mSpeechBubble == nullptr)
660
            createSpeechBubble();
661
        if (mSpeechBubble != nullptr)
662
        {
663
            mSpeechBubble->setCaption(isShowName ? mName : "");
664
            mSpeechBubble->setText(mSpeech, isShowName);
665
        }
666
    }
667
}
668
669
void Being::takeDamage(Being *restrict const attacker,
670
                       const int amount,
671
                       const AttackTypeT type,
672
                       const int attackId,
673
                       const int level) restrict2
674
{
675
    if (userPalette == nullptr || attacker == nullptr)
676
        return;
677
678
    BLOCK_START("Being::takeDamage1")
679
680
    Font *font = nullptr;
681
    const Color *color;
682
683
    if (gui != nullptr)
684
        font = gui->getInfoParticleFont();
685
686
    // Selecting the right color
687
    if (type == AttackType::CRITICAL || type == AttackType::FLEE)
688
    {
689
        if (type == AttackType::CRITICAL)
690
            attacker->setCriticalHit(amount);
691
692
        if (attacker == localPlayer)
693
        {
694
            color = &userPalette->getColor(
695
                UserColorId::HIT_LOCAL_PLAYER_CRITICAL);
696
        }
697
        else
698
        {
699
            color = &userPalette->getColor(UserColorId::HIT_CRITICAL);
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
        }
710
        else
711
        {
712
            color = &userPalette->getColor(UserColorId::MISS);
713
        }
714
    }
715
    else if (mType == ActorType::Monster ||
716
             mType == ActorType::Mercenary ||
717
             mType == ActorType::Pet ||
718
             mType == ActorType::Homunculus ||
719
             mType == ActorType::SkillUnit)
720
    {
721
        if (attacker == localPlayer)
722
        {
723
            color = &userPalette->getColor(
724
                UserColorId::HIT_LOCAL_PLAYER_MONSTER);
725
        }
726
        else
727
        {
728
            color = &userPalette->getColor(
729
                UserColorId::HIT_PLAYER_MONSTER);
730
        }
731
    }
732
    else if (mType == ActorType::Player &&
733
             attacker != localPlayer &&
734
             this == localPlayer)
735
    {
736
        // here player was attacked by other player. mark him as enemy.
737
        color = &userPalette->getColor(UserColorId::HIT_PLAYER_PLAYER);
738
        attacker->setEnemy(true);
739
        attacker->updateColors();
740
    }
741
    else
742
    {
743
        color = &userPalette->getColor(UserColorId::HIT_MONSTER_PLAYER);
744
    }
745
746
    if (chatWindow != nullptr && mShowBattleEvents)
747
    {
748
        if (this == localPlayer)
749
        {
750
            if (attacker->mType == ActorType::Player || (amount != 0))
751
            {
752
                ChatWindow::battleChatLog(strprintf("%s : Hit you  -%d",
753
                    attacker->getName().c_str(), amount),
754
                    ChatMsgType::BY_OTHER);
755
            }
756
        }
757
        else if (attacker == localPlayer && (amount != 0))
758
        {
759
            ChatWindow::battleChatLog(strprintf("%s : You hit %s -%d",
760
                attacker->mName.c_str(),
761
                mName.c_str(),
762
                amount),
763
                ChatMsgType::BY_PLAYER);
764
        }
765
    }
766
    if (font != nullptr && particleEngine != nullptr)
767
    {
768
        const std::string damage = amount != 0 ? toString(amount) :
769
            // TRANSLATORS: dodge or miss message in attacks
770
            type == AttackType::FLEE ? _("dodge") : _("miss");
771
        // Show damage number
772
        particleEngine->addTextSplashEffect(damage,
773
            mPixelX,
774
            mPixelY - 16,
775
            color,
776
            font,
777
            true);
778
    }
779
    BLOCK_END("Being::takeDamage1")
780
    BLOCK_START("Being::takeDamage2")
781
782
    if (type != AttackType::SKILL)
783
        attacker->updateHit(amount);
784
785
    if (amount > 0)
786
    {
787
        if ((localPlayer != nullptr) && localPlayer == this)
788
            localPlayer->setLastHitFrom(attacker->mName);
789
790
        mDamageTaken += amount;
791
        if (mInfo != nullptr)
792
        {
793
            playSfx(mInfo->getSound(ItemSoundEvent::HURT),
794
                this,
795
                false,
796
                mX,
797
                mY);
798
799
            if (!mInfo->isStaticMaxHP())
800
            {
801
                if ((mHP == 0) && mInfo->getMaxHP() < mDamageTaken)
802
                    mInfo->setMaxHP(mDamageTaken);
803
            }
804
        }
805
        if ((mHP != 0) && isAlive())
806
        {
807
            mHP -= amount;
808
            if (mHP < 0)
809
                mHP = 0;
810
        }
811
812
        if (mType == ActorType::Monster)
813
        {
814
            updatePercentHP();
815
            updateName();
816
        }
817
        else if (mType == ActorType::Player &&
818
                 (socialWindow != nullptr) &&
819
                 !mName.empty())
820
        {
821
            socialWindow->updateAvatar(mName);
822
        }
823
824
        if (effectManager != nullptr)
825
        {
826
            const int hitEffectId = getHitEffect(attacker,
827
                type,
828
                attackId,
829
                level);
830
            if (hitEffectId >= 0)
831
                effectManager->trigger(hitEffectId, this);
832
        }
833
    }
834
    else
835
    {
836
        if (effectManager != nullptr)
837
        {
838
            int hitEffectId = -1;
839
            if (type == AttackType::SKILL)
840
            {
841
                hitEffectId = getHitEffect(attacker,
842
                    AttackType::SKILLMISS,
843
                    attackId,
844
                    level);
845
            }
846
            else
847
            {
848
                hitEffectId = getHitEffect(attacker,
849
                    AttackType::MISS,
850
                    attackId,
851
                    level);
852
            }
853
            if (hitEffectId >= 0)
854
                effectManager->trigger(hitEffectId, this);
855
        }
856
    }
857
    BLOCK_END("Being::takeDamage2")
858
}
859
860
int Being::getHitEffect(const Being *restrict const attacker,
861
                        const AttackTypeT type,
862
                        const int attackId,
863
                        const int level) const restrict2
864
{
865
    if (effectManager == nullptr)
866
        return 0;
867
868
    BLOCK_START("Being::getHitEffect")
869
    // Init the particle effect path based on current
870
    // weapon or default.
871
    int hitEffectId = 0;
872
    if (type == AttackType::SKILL || type == AttackType::SKILLMISS)
873
    {
874
        const SkillData *restrict const data =
875
            skillDialog->getSkillDataByLevel(attackId, level);
876
        if (data == nullptr)
877
            return -1;
878
        if (type == AttackType::SKILL)
879
        {
880
            hitEffectId = data->hitEffectId;
881
            if (hitEffectId == -1)
882
                hitEffectId = paths.getIntValue("skillHitEffectId");
883
        }
884
        else
885
        {
886
            hitEffectId = data->missEffectId;
887
            if (hitEffectId == -1)
888
                hitEffectId = paths.getIntValue("skillMissEffectId");
889
        }
890
    }
891
    else
892
    {
893
        if (attacker != nullptr)
894
        {
895
            const ItemInfo *restrict const attackerWeapon
896
                = attacker->getEquippedWeapon();
897
            if (attackerWeapon != nullptr &&
898
                attacker->getType() == ActorType::Player)
899
            {
900
                if (type == AttackType::MISS)
901
                    hitEffectId = attackerWeapon->getMissEffectId();
902
                else if (type != AttackType::CRITICAL)
903
                    hitEffectId = attackerWeapon->getHitEffectId();
904
                else
905
                    hitEffectId = attackerWeapon->getCriticalHitEffectId();
906
            }
907
            else if (attacker->getType() == ActorType::Monster)
908
            {
909
                const BeingInfo *restrict const info = attacker->getInfo();
910
                if (info != nullptr)
911
                {
912
                    const Attack *restrict const atk =
913
                        info->getAttack(attackId);
914
                    if (atk != nullptr)
915
                    {
916
                        if (type == AttackType::MISS)
917
                            hitEffectId = atk->mMissEffectId;
918
                        else if (type != AttackType::CRITICAL)
919
                            hitEffectId = atk->mHitEffectId;
920
                        else
921
                            hitEffectId = atk->mCriticalHitEffectId;
922
                    }
923
                    else
924
                    {
925
                        hitEffectId = getDefaultEffectId(type);
926
                    }
927
                }
928
            }
929
            else
930
            {
931
                hitEffectId = getDefaultEffectId(type);
932
            }
933
        }
934
        else
935
        {
936
            hitEffectId = getDefaultEffectId(type);
937
        }
938
    }
939
    BLOCK_END("Being::getHitEffect")
940
    return hitEffectId;
941
}
942
943
int Being::getDefaultEffectId(const AttackTypeT &restrict type)
944
{
945
    if (type == AttackType::MISS)
946
        return paths.getIntValue("missEffectId");
947
    else if (type != AttackType::CRITICAL)
948
        return paths.getIntValue("hitEffectId");
949
    else
950
        return paths.getIntValue("criticalHitEffectId");
951
}
952
953
void Being::handleAttack(Being *restrict const victim,
954
                         const int damage,
955
                         const int attackId) restrict2
956
{
957
    if ((victim == nullptr) || (mInfo == nullptr))
958
        return;
959
960
    BLOCK_START("Being::handleAttack")
961
962
    if (this != localPlayer)
963
        setAction(BeingAction::ATTACK, attackId);
964
965
    mLastAttackX = victim->mX;
966
    mLastAttackY = victim->mY;
967
968
    if (mType == ActorType::Player && (mEquippedWeapon != nullptr))
969
        fireMissile(victim, mEquippedWeapon->getMissileConst());
970
    else if (mInfo->getAttack(attackId) != nullptr)
971
        fireMissile(victim, mInfo->getAttack(attackId)->mMissile);
972
973
    reset();
974
    mActionTime = tick_time;
975
976
    if (Net::getNetworkType() == ServerType::TMWATHENA &&
977
        this != localPlayer)
978
    {
979
        const uint8_t dir = calcDirection(victim->mX,
980
            victim->mY);
981
        if (dir != 0u)
982
            setDirection(dir);
983
    }
984
985
    if ((damage != 0) && victim->mType == ActorType::Player
986
        && victim->mAction == BeingAction::SIT)
987
    {
988
        victim->setAction(BeingAction::STAND, 0);
989
    }
990
991
    if (mType == ActorType::Player)
992
    {
993
        if (mSlots.size() >= 10)
994
        {
995
            // here 10 is weapon slot
996
            int weaponId = mSlots[10].spriteId;
997
            if (weaponId == 0)
998
                weaponId = -100 - toInt(mSubType, int);
999
            const ItemInfo &info = ItemDB::get(weaponId);
1000
            playSfx(info.getSound(
1001
                (damage > 0) ? ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1002
                victim,
1003
                true,
1004
                mX, mY);
1005
        }
1006
    }
1007
    else
1008
    {
1009
        playSfx(mInfo->getSound((damage > 0) ?
1010
            ItemSoundEvent::HIT : ItemSoundEvent::MISS), victim, true, mX, mY);
1011
    }
1012
    BLOCK_END("Being::handleAttack")
1013
}
1014
1015
void Being::handleSkillCasting(Being *restrict const victim,
1016
                               const int skillId,
1017
                               const int skillLevel) restrict2
1018
{
1019
    if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1020
        return;
1021
1022
    setAction(BeingAction::CAST, skillId);
1023
1024
    const SkillData *restrict const data = skillDialog->getSkillDataByLevel(
1025
        skillId,
1026
        skillLevel);
1027
1028
    if (data != nullptr)
1029
    {
1030
        effectManager->triggerDefault(data->castingSrcEffectId,
1031
            this,
1032
            paths.getIntValue("skillCastingSrcEffectId"));
1033
        effectManager->triggerDefault(data->castingDstEffectId,
1034
            victim,
1035
            paths.getIntValue("skillCastingDstEffectId"));
1036
        fireMissile(victim, data->castingMissile);
1037
    }
1038
}
1039
1040
void Being::handleSkill(Being *restrict const victim,
1041
                        const int damage,
1042
                        const int skillId,
1043
                        const int skillLevel) restrict2
1044
{
1045
    if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1046
        return;
1047
1048
    const SkillInfo *restrict const skill = skillDialog->getSkill(skillId);
1049
    const SkillData *restrict const data = skill != nullptr
1050
        ? skill->getData1(skillLevel) : nullptr;
1051
    if (data != nullptr)
1052
    {
1053
        effectManager->triggerDefault(data->srcEffectId,
1054
            this,
1055
            paths.getIntValue("skillSrcEffectId"));
1056
        effectManager->triggerDefault(data->dstEffectId,
1057
            victim,
1058
            paths.getIntValue("skillDstEffectId"));
1059
        fireMissile(victim, data->missile);
1060
    }
1061
1062
    if (this != localPlayer && (skill != nullptr))
1063
    {
1064
        const SkillType::SkillType type = skill->type;
1065
        if ((type & SkillType::Attack) != 0 ||
1066
            (type & SkillType::Ground) != 0)
1067
        {
1068
            setAction(BeingAction::ATTACK, 1);
1069
        }
1070
        else
1071
        {
1072
            setAction(BeingAction::STAND, 1);
1073
        }
1074
    }
1075
1076
    reset();
1077
    mActionTime = tick_time;
1078
1079
    if (Net::getNetworkType() == ServerType::TMWATHENA &&
1080
        this != localPlayer)
1081
    {
1082
        const uint8_t dir = calcDirection(victim->mX,
1083
            victim->mY);
1084
        if (dir != 0u)
1085
            setDirection(dir);
1086
    }
1087
    if ((damage != 0) && victim->mType == ActorType::Player
1088
        && victim->mAction == BeingAction::SIT)
1089
    {
1090
        victim->setAction(BeingAction::STAND, 0);
1091
    }
1092
    if (data != nullptr)
1093
    {
1094
        if (damage > 0)
1095
            playSfx(data->soundHit, victim, true, mX, mY);
1096
        else
1097
            playSfx(data->soundMiss, victim, true, mX, mY);
1098
    }
1099
    else
1100
    {
1101
        playSfx(mInfo->getSound((damage > 0) ?
1102
            ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1103
            victim,
1104
            true,
1105
            mX, mY);
1106
    }
1107
}
1108
1109
12
void Being::showNameBadge(const bool show) restrict2
1110
{
1111
12
    delete2(mBadges[BadgeIndex::Name]);
1112
12
    if (show &&
1113

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


12
    if (itr != mGuilds.end())
1245
        return itr->second;
1246
1247
    return nullptr;
1248
}
1249
1250
void Being::clearGuilds() restrict2
1251
{
1252
    FOR_EACH (GuildsMapCIter, itr, mGuilds)
1253
    {
1254
        Guild *const guild = itr->second;
1255
1256
        if (guild != nullptr)
1257
        {
1258
            if (this == localPlayer && (socialWindow != nullptr))
1259
                socialWindow->removeTab(guild);
1260
1261
            guild->removeMember(mId);
1262
        }
1263
    }
1264
1265
    mGuilds.clear();
1266
}
1267
1268
12
void Being::setParty(Party *restrict const party) restrict2
1269
{
1270
12
    if (party == mParty)
1271
        return;
1272
1273
12
    Party *const old = mParty;
1274
12
    mParty = party;
1275
1276
12
    if (old != nullptr)
1277
4
        old->removeMember(mId);
1278
1279
12
    if (party != nullptr)
1280
12
        party->addMember(mId, mName);
1281
1282
12
    updateColors();
1283
1284

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

36
    if (mShowBadges != BadgeDrawType::Hide &&
2277
18
        mBadgesCount != 0u)
2278
    {
2279
        if (mDispName != nullptr &&
2280
            gui != nullptr)
2281
        {
2282
            if (mShowBadges == BadgeDrawType::Right)
2283
            {
2284
                const Font *restrict const font = gui->getFont();
2285
                mBadgesX = mDispName->getX() + mDispName->getWidth();
2286
                mBadgesY = mDispName->getY() - font->getHeight();
2287
            }
2288
            else if (mShowBadges == BadgeDrawType::Bottom)
2289
            {
2290
                mBadgesX = px + 8 - mBadgesCount * 8;
2291
                if (mVisibleNamePos == VisibleNamePos::Bottom)
2292
                {
2293
                    mBadgesY = mDispName->getY();
2294
                }
2295
                else
2296
                {
2297
                    mBadgesY = py + settings.playerNameOffset + 16;
2298
                }
2299
            }
2300
            else
2301
            {
2302
                mBadgesX = px + 8 - mBadgesCount * 8;
2303
                if (mVisibleNamePos == VisibleNamePos::Top)
2304
                    mBadgesY = py - mDispName->getHeight();
2305
                else
2306
                    mBadgesY = py;
2307
            }
2308
        }
2309
        else
2310
        {
2311
            if (mShowBadges == BadgeDrawType::Right)
2312
            {
2313
                mBadgesX = px + settings.playerBadgeAtRightOffset;
2314
                mBadgesY = py;
2315
            }
2316
            else if (mShowBadges == BadgeDrawType::Bottom)
2317
            {
2318
                mBadgesX = px + 8 - mBadgesCount * 8;
2319
                const int height = settings.playerNameOffset;
2320
                if (mVisibleNamePos == VisibleNamePos::Bottom)
2321
                    mBadgesY = py + height;
2322
                else
2323
                    mBadgesY = py + height + 16;
2324
            }
2325
            else
2326
            {
2327
                mBadgesX = px + 8 - mBadgesCount * 8;
2328
                mBadgesY = py;
2329
            }
2330
        }
2331
    }
2332
18
}
2333
2334
void Being::drawEmotion(Graphics *restrict const graphics,
2335
                        const int offsetX,
2336
                        const int offsetY) const restrict2
2337
{
2338
    if (mErased)
2339
        return;
2340
2341
    const int px = mPixelX - offsetX - mapTileSize / 2;
2342
    const int py = mPixelY - offsetY - mapTileSize * 2 - mapTileSize;
2343
    if (mAnimationEffect != nullptr)
2344
        mAnimationEffect->draw(graphics, px, py);
2345
    if (mShowBadges != BadgeDrawType::Hide &&
2346
        mBadgesCount != 0u)
2347
    {
2348
        int x = mBadgesX - offsetX;
2349
        const int y = mBadgesY - offsetY;
2350
        for_each_badges()
2351
        {
2352
            const AnimatedSprite *restrict const sprite = mBadges[f];
2353
            if (sprite != nullptr)
2354
            {
2355
                sprite->draw(graphics, x, y);
2356
                x += 16;
2357
            }
2358
        }
2359
    }
2360
    if (mEmotionSprite != nullptr)
2361
        mEmotionSprite->draw(graphics, px, py);
2362
}
2363
2364
void Being::drawSpeech(const int offsetX,
2365
                       const int offsetY) restrict2
2366
{
2367
    if (mErased)
2368
        return;
2369
    if (mSpeech.empty())
2370
        return;
2371
2372
    const int px = mPixelX - offsetX;
2373
    const int py = mPixelY - offsetY;
2374
    const int speech = mSpeechType;
2375
2376
    // Draw speech above this being
2377
    if (mSpeechTime == 0)
2378
    {
2379
        if (mSpeechBubble != nullptr &&
2380
            mSpeechBubble->mVisible == Visible_true)
2381
        {
2382
            mSpeechBubble->setVisible(Visible_false);
2383
        }
2384
        mSpeech.clear();
2385
    }
2386
    else if (mSpeechTime > 0 && (speech == BeingSpeech::NAME_IN_BUBBLE ||
2387
             speech == BeingSpeech::NO_NAME_IN_BUBBLE))
2388
    {
2389
        delete2(mText)
2390
2391
        if (mSpeechBubble != nullptr)
2392
        {
2393
            mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
2394
                py - getHeight() - (mSpeechBubble->getHeight()));
2395
            mSpeechBubble->setVisible(Visible_true);
2396
            mSpeechBubble->requestMoveToBackground();
2397
        }
2398
    }
2399
    else if (mSpeechTime > 0 && speech == BeingSpeech::TEXT_OVERHEAD)
2400
    {
2401
        if (mSpeechBubble != nullptr)
2402
            mSpeechBubble->setVisible(Visible_false);
2403
2404
        if ((mText == nullptr) && (userPalette != nullptr))
2405
        {
2406
            mText = new Text(mSpeech,
2407
                mPixelX,
2408
                mPixelY - getHeight(),
2409
                Graphics::CENTER,
2410
                &theme->getColor(ThemeColorId::BUBBLE_TEXT, 255),
2411
                Speech_true);
2412
            mText->adviseXY(mPixelX,
2413
                (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
2414
                mMoveNames);
2415
        }
2416
    }
2417
    else if (speech == BeingSpeech::NO_SPEECH)
2418
    {
2419
        if (mSpeechBubble != nullptr)
2420
            mSpeechBubble->setVisible(Visible_false);
2421
        delete2(mText)
2422
    }
2423
}
2424
2425
template<signed char pos, signed char neg>
2426
int Being::getOffset() const restrict2
2427
{
2428
    // Check whether we're walking in the requested direction
2429
    if (mAction != BeingAction::MOVE || !(mDirection & (pos | neg)))
2430
        return 0;
2431
2432
    int offset = 0;
2433
2434
    if (mMap && mSpeed)
2435
    {
2436
        const int time = get_elapsed_time(mActionTime);
2437
        offset = (pos == BeingDirection::LEFT &&
2438
            neg == BeingDirection::RIGHT) ?
2439
            (time * mMap->getTileWidth() / mSpeed)
2440
            : (time * mMap->getTileHeight() / mSpeed);
2441
    }
2442
2443
    // We calculate the offset _from_ the _target_ location
2444
    offset -= mapTileSize;
2445
    if (offset > 0)
2446
        offset = 0;
2447
2448
    // Going into negative direction? Invert the offset.
2449
    if (mDirection & pos)
2450
        offset = -offset;
2451
2452
    if (offset > mapTileSize)
2453
        offset = mapTileSize;
2454
    if (offset < -mapTileSize)
2455
        offset = -mapTileSize;
2456
2457
    return offset;
2458
}
2459
2460
18
void Being::updateCoords() restrict2
2461
{
2462
18
    if (mDispName != nullptr)
2463
    {
2464
18
        int offsetX = mPixelX;
2465
18
        int offsetY = mPixelY;
2466
18
        if (mInfo != nullptr)
2467
        {
2468
            offsetX += mInfo->getNameOffsetX();
2469
            offsetY += mInfo->getNameOffsetY();
2470
        }
2471
        // Monster names show above the sprite instead of below it
2472

34
        if (mType == ActorType::Monster ||
2473
16
            mVisibleNamePos == VisibleNamePos::Top)
2474
        {
2475
4
            offsetY += - settings.playerNameOffset - mDispName->getHeight();
2476
        }
2477
18
        mDispName->adviseXY(offsetX, offsetY, mMoveNames);
2478
    }
2479
18
    updateBadgesPosition();
2480
18
}
2481
2482
void Being::optionChanged(const std::string &restrict value) restrict2
2483
{
2484
    if (mType == ActorType::Player && value == "visiblenames")
2485
    {
2486
        setShowName(config.getIntValue("visiblenames") == VisibleName::Show);
2487
        updateBadgesPosition();
2488
    }
2489
}
2490
2491
void Being::flashName(const int time) restrict2
2492
{
2493
    if (mDispName != nullptr)
2494
        mDispName->flash(time);
2495
}
2496
2497
std::string Being::getGenderSignWithSpace() const restrict2
2498
{
2499
    const std::string &restrict str = getGenderSign();
2500
    if (str.empty())
2501
        return str;
2502
    else
2503
        return std::string(" ").append(str);
2504
}
2505
2506
std::string Being::getGenderSign() const restrict2
2507
{
2508
    std::string str;
2509
    if (mShowGender)
2510
    {
2511
        if (getGender() == Gender::FEMALE)
2512
            str = "\u2640";
2513
        else if (getGender() == Gender::MALE)
2514
            str = "\u2642";
2515
    }
2516
    if (mShowPlayersStatus &&
2517
        mShowBadges == BadgeDrawType::Hide)
2518
    {
2519
        if (mShop)
2520
            str.append("$");
2521
        if (mAway)
2522
        {
2523
            // TRANSLATORS: this away status writed in player nick
2524
            str.append(_("A"));
2525
        }
2526
        else if (mInactive)
2527
        {
2528
            // TRANSLATORS: this inactive status writed in player nick
2529
            str.append(_("I"));
2530
        }
2531
    }
2532
    return str;
2533
}
2534
2535
218
void Being::showName() restrict2
2536
{
2537
436
    if (mName.empty())
2538
200
        return;
2539
2540
36
    delete2(mDispName);
2541
2542

18
    if (mHideErased && playerRelations.getRelation(mName) == Relation::ERASED)
2543
        return;
2544
2545
54
    std::string displayName(mName);
2546
2547

18
    if (mType != ActorType::Monster && (mShowGender || mShowLevel))
2548
    {
2549
        displayName.append(" ");
2550
        if (mShowLevel && getLevel() != 0)
2551
            displayName.append(toString(getLevel()));
2552
2553
        displayName.append(getGenderSign());
2554
    }
2555
2556
18
    if (mType == ActorType::Monster)
2557
    {
2558

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

8
            displayName.append(", ").append(toString(getDamageTaken()));
2560
    }
2561
2562
18
    Font *font = nullptr;
2563

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

22
        && mType != ActorType::Monster)
2565
    {
2566
2
        font = boldFont;
2567
    }
2568
16
    else if (mType == ActorType::Player
2569


30
             && !playerRelations.isGoodName(this) && (gui != nullptr))
2570
    {
2571
        font = gui->getSecureFont();
2572
    }
2573
2574
18
    if (mInfo != nullptr)
2575
    {
2576
        mDispName = new FlashText(displayName,
2577
            mPixelX + mInfo->getNameOffsetX(),
2578
            mPixelY + mInfo->getNameOffsetY(),
2579
            Graphics::CENTER,
2580
            mNameColor,
2581
            font);
2582
    }
2583
    else
2584
    {
2585
18
        mDispName = new FlashText(displayName,
2586
            mPixelX,
2587
            mPixelY,
2588
            Graphics::CENTER,
2589
            mNameColor,
2590

18
            font);
2591
    }
2592
2593
18
    updateCoords();
2594
}
2595
2596
140
void Being::setDefaultNameColor(const UserColorIdT defaultColor) restrict2
2597
{
2598

140
    switch (mTeamId)
2599
    {
2600
140
        case 0:
2601
        default:
2602
140
            mNameColor = &userPalette->getColor(defaultColor);
2603
140
            break;
2604
        case 1:
2605
            mNameColor = &userPalette->getColor(UserColorId::TEAM1);
2606
            break;
2607
        case 2:
2608
            mNameColor = &userPalette->getColor(UserColorId::TEAM2);
2609
            break;
2610
        case 3:
2611
            mNameColor = &userPalette->getColor(UserColorId::TEAM3);
2612
            break;
2613
    }
2614
140
}
2615
2616
216
void Being::updateColors()
2617
{
2618
216
    if (userPalette != nullptr)
2619
    {
2620
140
        if (mType == ActorType::Monster)
2621
        {
2622
            setDefaultNameColor(UserColorId::MONSTER);
2623
            mTextColor = &userPalette->getColor(UserColorId::MONSTER);
2624
        }
2625
140
        else if (mType == ActorType::Npc)
2626
        {
2627
            setDefaultNameColor(UserColorId::NPC);
2628
            mTextColor = &userPalette->getColor(UserColorId::NPC);
2629
        }
2630
140
        else if (mType == ActorType::Pet)
2631
        {
2632
            setDefaultNameColor(UserColorId::PET);
2633
            mTextColor = &userPalette->getColor(UserColorId::PET);
2634
        }
2635
140
        else if (mType == ActorType::Homunculus)
2636
        {
2637
            setDefaultNameColor(UserColorId::HOMUNCULUS);
2638
            mTextColor = &userPalette->getColor(UserColorId::HOMUNCULUS);
2639
        }
2640
140
        else if (mType == ActorType::SkillUnit)
2641
        {
2642
            setDefaultNameColor(UserColorId::SKILLUNIT);
2643
            mTextColor = &userPalette->getColor(UserColorId::SKILLUNIT);
2644
        }
2645
140
        else if (this == localPlayer)
2646
        {
2647
            mNameColor = &userPalette->getColor(UserColorId::SELF);
2648
            mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2649
        }
2650
        else
2651
        {
2652
140
            mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2653
2654
140
            if (playerRelations.getRelation(mName) != Relation::ERASED)
2655
140
                mErased = false;
2656
            else
2657
                mErased = true;
2658
2659
140
            if (mIsGM)
2660
            {
2661
                mTextColor = &userPalette->getColor(UserColorId::GM);
2662
                mNameColor = &userPalette->getColor(UserColorId::GM);
2663
            }
2664
140
            else if (mEnemy)
2665
            {
2666
                mNameColor = &userPalette->getColor(UserColorId::ENEMY);
2667
            }
2668
140
            else if ((mParty != nullptr) && (localPlayer != nullptr)
2669

140
                     && mParty == localPlayer->getParty())
2670
            {
2671
                mNameColor = &userPalette->getColor(UserColorId::PARTY);
2672
            }
2673
140
            else if ((localPlayer != nullptr) && (getGuild() != nullptr)
2674

140
                     && getGuild() == localPlayer->getGuild())
2675
            {
2676
                mNameColor = &userPalette->getColor(UserColorId::GUILD);
2677
            }
2678
140
            else if (playerRelations.getRelation(mName) == Relation::FRIEND)
2679
            {
2680
                mNameColor = &userPalette->getColor(UserColorId::FRIEND);
2681
            }
2682
140
            else if (playerRelations.getRelation(mName) ==
2683
                     Relation::DISREGARDED
2684

140
                     || playerRelations.getRelation(mName) ==
2685
                     Relation::BLACKLISTED)
2686
            {
2687
                mNameColor = &userPalette->getColor(UserColorId::DISREGARDED);
2688
            }
2689
140
            else if (playerRelations.getRelation(mName)
2690

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




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