GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/being.cpp Lines: 203 2616 7.8 %
Date: 2019-06-27 Branches: 145 2840 5.1 %

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

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

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

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

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

12
    if (show &&
1122

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


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

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

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

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

9
    if (mHideErased && playerRelations.getRelation(mName) == Relation::ERASED)
2583
        return;
2584
2585
27
    std::string displayName(mName);
2586
2587

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

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

3
            displayName.append(", ").append(toString(getDamageTaken()));
2600
    }
2601
2602
9
    Font *font = nullptr;
2603

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

11
        && mType != ActorType::Monster)
2605
    {
2606
1
        font = boldFont;
2607
    }
2608
16
    else if (mType == ActorType::Player
2609


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

9
            font);
2631
    }
2632
2633
9
    updateCoords();
2634
}
2635
2636
71
void Being::setDefaultNameColor(const UserColorIdT defaultColor) restrict2
2637
{
2638

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

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

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

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

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

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

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




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