1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2004-2009  The Mana World Development Team
4  *  Copyright (C) 2009-2010  The Mana Developers
5  *  Copyright (C) 2011-2019  The ManaPlus Developers
6  *  Copyright (C) 2019-2021  Andrei Karas
7  *
8  *  This file is part of The ManaPlus Client.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "being/being.h"
25 
26 #include "actormanager.h"
27 #include "beingequipbackend.h"
28 #include "configuration.h"
29 #include "effectmanager.h"
30 #include "guild.h"
31 #include "itemcolormanager.h"
32 #include "party.h"
33 #include "settings.h"
34 #include "soundmanager.h"
35 #include "text.h"
36 
37 #include "being/beingcacheentry.h"
38 #include "being/beingflag.h"
39 #include "being/beingspeech.h"
40 #include "being/castingeffect.h"
41 #include "being/localplayer.h"
42 #include "being/playerinfo.h"
43 #include "being/playerrelations.h"
44 #include "being/homunculusinfo.h"
45 #include "being/mercenaryinfo.h"
46 
47 #include "const/utils/timer.h"
48 
49 #include "const/resources/spriteaction.h"
50 
51 #include "enums/being/beingdirection.h"
52 
53 #include "enums/resources/map/blockmask.h"
54 
55 #include "fs/files.h"
56 
57 #include "gui/gui.h"
58 #include "gui/userpalette.h"
59 
60 #include "gui/fonts/font.h"
61 
62 #include "gui/popups/speechbubble.h"
63 
64 #include "gui/windows/chatwindow.h"
65 #include "gui/windows/equipmentwindow.h"
66 #include "gui/windows/skilldialog.h"
67 #include "gui/windows/socialwindow.h"
68 
69 #include "net/charserverhandler.h"
70 #include "net/gamehandler.h"
71 #include "net/homunculushandler.h"
72 #include "net/mercenaryhandler.h"
73 #include "net/net.h"
74 #include "net/npchandler.h"
75 #include "net/packetlimiter.h"
76 #include "net/playerhandler.h"
77 #include "net/serverfeatures.h"
78 
79 #include "particle/particleinfo.h"
80 
81 #include "resources/attack.h"
82 #include "resources/chatobject.h"
83 #include "resources/emoteinfo.h"
84 #include "resources/emotesprite.h"
85 #include "resources/horseinfo.h"
86 #include "resources/iteminfo.h"
87 
88 #include "resources/db/avatardb.h"
89 #include "resources/db/badgesdb.h"
90 #include "resources/db/groupdb.h"
91 #include "resources/db/elementaldb.h"
92 #include "resources/db/emotedb.h"
93 #include "resources/db/homunculusdb.h"
94 #include "resources/db/horsedb.h"
95 #include "resources/db/languagedb.h"
96 #include "resources/db/mercenarydb.h"
97 #include "resources/db/monsterdb.h"
98 #include "resources/db/npcdb.h"
99 #include "resources/db/petdb.h"
100 #include "resources/db/skillunitdb.h"
101 
102 #include "resources/image/image.h"
103 
104 #include "resources/item/item.h"
105 
106 #include "resources/map/map.h"
107 
108 #include "resources/skill/skilldata.h"
109 #include "resources/skill/skillinfo.h"
110 
111 #include "resources/sprite/animatedsprite.h"
112 
113 #include "gui/widgets/createwidget.h"
114 
115 #include "utils/checkutils.h"
116 #include "utils/delete2.h"
117 #include "utils/foreach.h"
118 #include "utils/gettext.h"
119 #include "utils/likely.h"
120 #include "utils/stdmove.h"
121 #include "utils/timer.h"
122 
123 #include "debug.h"
124 
125 const unsigned int CACHE_SIZE = 50;
126 
127 time_t Being::mUpdateConfigTime = 0;
128 unsigned int Being::mConfLineLim = 0;
129 int Being::mSpeechType = 0;
130 bool Being::mHighlightMapPortals = false;
131 bool Being::mHighlightMonsterAttackRange = false;
132 bool Being::mLowTraffic = true;
133 bool Being::mDrawHotKeys = true;
134 bool Being::mShowBattleEvents = false;
135 bool Being::mShowMobHP = false;
136 bool Being::mShowOwnHP = false;
137 bool Being::mShowGender = false;
138 bool Being::mShowLevel = false;
139 bool Being::mShowPlayersStatus = false;
140 bool Being::mEnableReorderSprites = true;
141 bool Being::mHideErased = false;
142 Move Being::mMoveNames = Move_false;
143 bool Being::mUseDiagonal = true;
144 BadgeDrawType::Type Being::mShowBadges = BadgeDrawType::Top;
145 int Being::mAwayEffect = -1;
146 VisibleNamePos::Type Being::mVisibleNamePos = VisibleNamePos::Bottom;
147 
148 std::list<BeingCacheEntry*> beingInfoCache;
149 typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
150 typedef std::map<int, int>::const_iterator IntMapCIter;
151 
152 static const unsigned int SPEECH_TIME = 500;
153 static const unsigned int SPEECH_MIN_TIME = 200;
154 static const unsigned int SPEECH_MAX_TIME = 800;
155 
156 #define for_each_badges() \
157     for (int f = 0; f < BadgeIndex::BadgeIndexSize; f++)
158 
159 #define for_each_horses(name) \
160     FOR_EACH (STD_VECTOR<AnimatedSprite*>::const_iterator, it, name)
161 
Being(const BeingId id,const ActorTypeT type)162 Being::Being(const BeingId id,
163              const ActorTypeT type) :
164     ActorSprite(id),
165     mNextSound(),
166     mInfo(BeingInfo::unknown),
167     mEmotionSprite(nullptr),
168     mAnimationEffect(nullptr),
169     mCastingEffect(nullptr),
170     mBadges(),
171     mSpriteAction(SpriteAction::STAND),
172     mName(),
173     mExtName(),
174     mRaceName(),
175     mPartyName(),
176     mGuildName(),
177     mClanName(),
178     mSpeech(),
179     mDispName(nullptr),
180     mNameColor(nullptr),
181     mEquippedWeapon(nullptr),
182     mPath(),
183     mText(nullptr),
184     mTextColor(nullptr),
185     mDest(),
186     mSlots(),
187     mSpriteParticles(),
188     mGuilds(),
189     mParty(nullptr),
190     mActionTime(0),
191     mEmotionTime(0),
192     mSpeechTime(0),
193     mAttackSpeed(350),
194     mLevel(0),
195     mGroupId(0),
196     mAttackRange(1),
197     mLastAttackX(0),
198     mLastAttackY(0),
199     mPreStandTime(0),
200     mGender(Gender::UNSPECIFIED),
201     mAction(BeingAction::STAND),
202     mSubType(fromInt(0xFFFF, BeingTypeId)),
203     mDirection(BeingDirection::DOWN),
204     mDirectionDelayed(0),
205     mSpriteDirection(SpriteDirection::DOWN),
206     mShowName(false),
207     mIsGM(false),
208     mType(type),
209     mSpeechBubble(nullptr),
210     mWalkSpeed(playerHandler != nullptr ?
211                playerHandler->getDefaultWalkSpeed() : 1),
212     mSpeed(playerHandler != nullptr ?
213            playerHandler->getDefaultWalkSpeed() : 1),
214     mIp(),
215     mSpriteRemap(new int[20]),
216     mSpriteHide(new int[20]),
217     mSpriteDraw(new int[20]),
218     mComment(),
219     mBuyBoard(),
220     mSellBoard(),
221     mOwner(nullptr),
222     mSpecialParticle(nullptr),
223     mChat(nullptr),
224     mHorseInfo(nullptr),
225     mDownHorseSprites(),
226     mUpHorseSprites(),
227     mSpiritParticles(),
228     mX(0),
229     mY(0),
230     mCachedX(0),
231     mCachedY(0),
232     mSortOffsetY(0),
233     mPixelOffsetY(0),
234     mFixedOffsetY(0),
235     mOldHeight(0),
236     mDamageTaken(0),
237     mHP(0),
238     mMaxHP(0),
239     mDistance(0),
240     mReachable(Reachable::REACH_UNKNOWN),
241     mGoodStatus(-1),
242     mMoveTime(0),
243     mAttackTime(0),
244     mTalkTime(0),
245     mOtherTime(0),
246     mTestTime(cur_time),
247     mAttackDelay(0),
248     mMinHit(0),
249     mMaxHit(0),
250     mCriticalHit(0),
251     mPvpRank(0),
252     mNumber(100),
253     mSpiritBalls(0U),
254     mUsageCounter(1),
255     mKarma(0),
256     mManner(0),
257     mAreaSize(11),
258     mCastEndTime(0),
259     mLanguageId(-1),
260     mBadgesX(0),
261     mBadgesY(0),
262     mCreatorId(BeingId_zero),
263     mTeamId(0U),
264     mLook(0U),
265     mBadgesCount(0U),
266     mHairColor(ItemColor_zero),
267     mErased(false),
268     mEnemy(false),
269     mGotComment(false),
270     mAdvanced(false),
271     mShop(false),
272     mAway(false),
273     mInactive(false),
274     mNeedPosUpdate(true),
275     mBotAi(true),
276     mAllowNpcEquipment(false)
277 {
278     for (int f = 0; f < 20; f ++)
279     {
280         mSpriteRemap[f] = f;
281         mSpriteHide[f] = 0;
282         mSpriteDraw[f] = 0;
283     }
284 
285     for_each_badges()
286         mBadges[f] = nullptr;
287 }
288 
postInit(const BeingTypeId subtype,Map * const map)289 void Being::postInit(const BeingTypeId subtype,
290                      Map *const map)
291 {
292     setMap(map);
293     setSubtype(subtype, 0);
294 
295     VisibleName::Type showName1 = VisibleName::Hide;
296 
297     switch (mType)
298     {
299         case ActorType::Player:
300         case ActorType::Mercenary:
301         case ActorType::Pet:
302         case ActorType::Homunculus:
303         case ActorType::Elemental:
304             showName1 = static_cast<VisibleName::Type>(
305                 config.getIntValue("visiblenames"));
306             break;
307         case ActorType::Portal:
308         case ActorType::SkillUnit:
309             showName1 = VisibleName::Hide;
310             break;
311         default:
312         case ActorType::Unknown:
313         case ActorType::Npc:
314         case ActorType::Monster:
315         case ActorType::FloorItem:
316         case ActorType::Avatar:
317             break;
318     }
319 
320     if (mType != ActorType::Npc)
321         mGotComment = true;
322 
323     config.addListener("visiblenames", this);
324 
325     reReadConfig();
326 
327     if (mType == ActorType::Npc ||
328         showName1 == VisibleName::Show)
329     {
330         setShowName(true);
331     }
332 
333     updateColors();
334     updatePercentHP();
335 }
336 
~Being()337 Being::~Being()
338 {
339     config.removeListener("visiblenames", this);
340     CHECKLISTENERS
341 
342     delete [] mSpriteRemap;
343     mSpriteRemap = nullptr;
344     delete [] mSpriteHide;
345     mSpriteHide = nullptr;
346     delete [] mSpriteDraw;
347     mSpriteDraw = nullptr;
348 
349     for_each_badges()
350         delete2(mBadges[f])
351 
352     delete2(mSpeechBubble)
353     delete2(mDispName)
354     delete2(mText)
355     delete2(mEmotionSprite)
356     delete2(mAnimationEffect)
357     delete2(mCastingEffect)
358     mBadgesCount = 0;
359     delete2(mChat)
360     removeHorse();
361 
362     removeAllItemsParticles();
363     mSpiritParticles.clear();
364 }
365 
createSpeechBubble()366 void Being::createSpeechBubble() restrict2
367 {
368     CREATEWIDGETV0(mSpeechBubble, SpeechBubble);
369 }
370 
setSubtype(const BeingTypeId subtype,const uint16_t look)371 void Being::setSubtype(const BeingTypeId subtype,
372                        const uint16_t look) restrict2
373 {
374     if (mInfo == nullptr)
375         return;
376 
377     if (subtype == mSubType && mLook == look)
378         return;
379 
380     mSubType = subtype;
381     mLook = look;
382 
383     switch (mType)
384     {
385         case ActorType::Monster:
386             mInfo = MonsterDB::get(mSubType);
387             if (mInfo != nullptr)
388             {
389                 setName(mInfo->getName());
390                 setupSpriteDisplay(mInfo->getDisplay(),
391                     ForceDisplay_true,
392                     DisplayType::Item,
393                     mInfo->getColor(fromInt(mLook, ItemColor)));
394                 mYDiff = mInfo->getSortOffsetY();
395             }
396             break;
397         case ActorType::Pet:
398             mInfo = PETDB::get(mSubType);
399             if (mInfo != nullptr)
400             {
401                 setName(mInfo->getName());
402                 setupSpriteDisplay(mInfo->getDisplay(),
403                     ForceDisplay_true,
404                     DisplayType::Item,
405                     mInfo->getColor(fromInt(mLook, ItemColor)));
406                 mYDiff = mInfo->getSortOffsetY();
407             }
408             break;
409         case ActorType::Mercenary:
410             mInfo = MercenaryDB::get(mSubType);
411             if (mInfo != nullptr)
412             {
413                 setName(mInfo->getName());
414                 setupSpriteDisplay(mInfo->getDisplay(),
415                     ForceDisplay_true,
416                     DisplayType::Item,
417                     mInfo->getColor(fromInt(mLook, ItemColor)));
418                 mYDiff = mInfo->getSortOffsetY();
419             }
420             break;
421         case ActorType::Homunculus:
422             mInfo = HomunculusDB::get(mSubType);
423             if (mInfo != nullptr)
424             {
425                 setName(mInfo->getName());
426                 setupSpriteDisplay(mInfo->getDisplay(),
427                     ForceDisplay_true,
428                     DisplayType::Item,
429                     mInfo->getColor(fromInt(mLook, ItemColor)));
430                 mYDiff = mInfo->getSortOffsetY();
431             }
432             break;
433         case ActorType::SkillUnit:
434             mInfo = SkillUnitDb::get(mSubType);
435             if (mInfo != nullptr)
436             {
437                 setName(mInfo->getName());
438                 setupSpriteDisplay(mInfo->getDisplay(),
439                     ForceDisplay_false,
440                     DisplayType::Item,
441                     mInfo->getColor(fromInt(mLook, ItemColor)));
442                 mYDiff = mInfo->getSortOffsetY();
443             }
444             break;
445         case ActorType::Elemental:
446             mInfo = ElementalDb::get(mSubType);
447             if (mInfo != nullptr)
448             {
449                 setName(mInfo->getName());
450                 setupSpriteDisplay(mInfo->getDisplay(),
451                     ForceDisplay_false,
452                     DisplayType::Item,
453                     mInfo->getColor(fromInt(mLook, ItemColor)));
454                 mYDiff = mInfo->getSortOffsetY();
455             }
456             break;
457         case ActorType::Npc:
458             mInfo = NPCDB::get(mSubType);
459             if (mInfo != nullptr)
460             {
461                 setupSpriteDisplay(mInfo->getDisplay(),
462                     ForceDisplay_false,
463                     DisplayType::Item,
464                     std::string());
465                 mYDiff = mInfo->getSortOffsetY();
466                 mAllowNpcEquipment = mInfo->getAllowEquipment();
467             }
468             break;
469         case ActorType::Avatar:
470             mInfo = AvatarDB::get(mSubType);
471             if (mInfo != nullptr)
472             {
473                 setupSpriteDisplay(mInfo->getDisplay(),
474                     ForceDisplay_false,
475                     DisplayType::Item,
476                     std::string());
477             }
478             break;
479         case ActorType::Player:
480         {
481             int id = -100 - toInt(subtype, int);
482             // Prevent showing errors when sprite doesn't exist
483             if (!ItemDB::exists(id))
484             {
485                 id = -100;
486                 // TRANSLATORS: default race name
487                 setRaceName(_("Human"));
488                 if (charServerHandler != nullptr)
489                 {
490                     setSpriteId(charServerHandler->baseSprite(),
491                         id);
492                 }
493             }
494             else
495             {
496                 const ItemInfo &restrict info = ItemDB::get(id);
497                 setRaceName(info.getName());
498                 if (charServerHandler != nullptr)
499                 {
500                     setSpriteColor(charServerHandler->baseSprite(),
501                         id,
502                         info.getColor(fromInt(mLook, ItemColor)));
503                 }
504             }
505             break;
506         }
507         case ActorType::Portal:
508             break;
509         case ActorType::Unknown:
510         case ActorType::FloorItem:
511         default:
512             reportAlways("Wrong being type %d in setSubType",
513                 CAST_S32(mType))
514             break;
515     }
516 }
517 
getTargetCursorSize() const518 TargetCursorSizeT Being::getTargetCursorSize() const restrict2
519 {
520     if (mInfo == nullptr)
521         return TargetCursorSize::SMALL;
522 
523     return mInfo->getTargetCursorSize();
524 }
525 
setPixelPositionF(const Vector & restrict pos)526 void Being::setPixelPositionF(const Vector &restrict pos) restrict2
527 {
528     Actor::setPixelPositionF(pos);
529 
530     updateCoords();
531 
532     if (mText != nullptr)
533     {
534         mText->adviseXY(CAST_S32(pos.x),
535             CAST_S32(pos.y) - getHeight() - mText->getHeight() - 9,
536             mMoveNames);
537     }
538 }
539 
setDestination(const int dstX,const int dstY)540 void Being::setDestination(const int dstX,
541                            const int dstY) restrict2
542 {
543     if (mMap == nullptr)
544         return;
545 
546     setPath(mMap->findPath(mX,
547         mY,
548         dstX,
549         dstY,
550         getBlockWalkMask(),
551         20));
552 }
553 
clearPath()554 void Being::clearPath() restrict2
555 {
556     mPath.clear();
557 }
558 
setPath(const Path & restrict path)559 void Being::setPath(const Path &restrict path) restrict2
560 {
561     mPath = path;
562     if (mPath.empty())
563         return;
564 
565     if (mAction != BeingAction::MOVE && mAction != BeingAction::DEAD)
566     {
567         nextTile();
568         mActionTime = tick_time;
569     }
570 }
571 
setSpeech(const std::string & restrict text)572 void Being::setSpeech(const std::string &restrict text) restrict2
573 {
574     if (userPalette == nullptr)
575         return;
576 
577     // Remove colors
578     mSpeech = removeColors(text);
579 
580     // Trim whitespace
581     trim(mSpeech);
582 
583     const unsigned int lineLim = mConfLineLim;
584     if (lineLim > 0 && mSpeech.length() > lineLim)
585         mSpeech = mSpeech.substr(0, lineLim);
586 
587     trim(mSpeech);
588     if (mSpeech.empty())
589         return;
590 
591     const size_t sz = mSpeech.size();
592     int time = 0;
593     if (sz < 200)
594         time = CAST_S32(SPEECH_TIME - 300 + (3 * sz));
595 
596     if (time < CAST_S32(SPEECH_MIN_TIME))
597         time = CAST_S32(SPEECH_MIN_TIME);
598 
599     // Check for links
600     size_t start = mSpeech.find('[');
601     size_t e = mSpeech.find(']', start);
602 
603     while (start != std::string::npos && e != std::string::npos)
604     {
605         // Catch multiple embeds and ignore them so it doesn't crash the client.
606         while ((mSpeech.find('[', start + 1) != std::string::npos) &&
607                (mSpeech.find('[', start + 1) < e))
608         {
609             start = mSpeech.find('[', start + 1);
610         }
611 
612         size_t position = mSpeech.find('|');
613         if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
614         {
615             mSpeech.erase(e, 1);
616             mSpeech.erase(start, (position - start) + 1);
617         }
618         position = mSpeech.find("@@");
619 
620         while (position != std::string::npos)
621         {
622             mSpeech.erase(position, 2);
623             position = mSpeech.find('@');
624         }
625 
626         start = mSpeech.find('[', start + 1);
627         e = mSpeech.find(']', start);
628     }
629 
630     if (!mSpeech.empty())
631     {
632         mSpeechTime = time <= CAST_S32(SPEECH_MAX_TIME)
633             ? time : CAST_S32(SPEECH_MAX_TIME);
634     }
635 
636     const int speech = mSpeechType;
637     if (speech == BeingSpeech::TEXT_OVERHEAD)
638     {
639         delete mText;
640         mText = nullptr;
641         mText = new Text(mSpeech,
642             mPixelX,
643             mPixelY - getHeight(),
644             Graphics::CENTER,
645             &userPalette->getColor(UserColorId::PARTICLE, 255U),
646             Speech_true,
647             nullptr);
648         mText->adviseXY(mPixelX,
649             (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
650             mMoveNames);
651     }
652     else
653     {
654         if (mSpeechBubble == nullptr)
655             createSpeechBubble();
656         if (mSpeechBubble != nullptr)
657         {
658             const bool isShowName = (speech == BeingSpeech::NAME_IN_BUBBLE);
659             mSpeechBubble->setCaption(isShowName ? mName : "",
660                 &theme->getColor(ThemeColorId::BUBBLE_NAME, 255),
661                 &theme->getColor(ThemeColorId::BUBBLE_NAME_OUTLINE, 255));
662             mSpeechBubble->setText(mSpeech, isShowName);
663         }
664     }
665 }
666 
takeDamage(Being * restrict const attacker,const int amount,const AttackTypeT type,const int attackId,const int level)667 void Being::takeDamage(Being *restrict const attacker,
668                        const int amount,
669                        const AttackTypeT type,
670                        const int attackId,
671                        const int level) restrict2
672 {
673     if (userPalette == nullptr || attacker == nullptr)
674         return;
675 
676     BLOCK_START("Being::takeDamage1")
677 
678     Font *font = nullptr;
679     const Color *color;
680 
681     if (gui != nullptr)
682         font = gui->getInfoParticleFont();
683 
684     // Selecting the right color
685     if (type == AttackType::CRITICAL || type == AttackType::FLEE)
686     {
687         if (type == AttackType::CRITICAL)
688             attacker->setCriticalHit(amount);
689 
690         if (attacker == localPlayer)
691         {
692             color = &userPalette->getColor(
693                 UserColorId::HIT_LOCAL_PLAYER_CRITICAL,
694                 255U);
695         }
696         else
697         {
698             color = &userPalette->getColor(UserColorId::HIT_CRITICAL,
699                 255U);
700         }
701     }
702     else if (amount == 0)
703     {
704         if (attacker == localPlayer)
705         {
706             // This is intended to be the wrong direction to visually
707             // differentiate between hits and misses
708             color = &userPalette->getColor(UserColorId::HIT_LOCAL_PLAYER_MISS,
709                 255U);
710         }
711         else
712         {
713             color = &userPalette->getColor(UserColorId::MISS,
714                 255U);
715         }
716     }
717     else if (mType == ActorType::Monster ||
718              mType == ActorType::Mercenary ||
719              mType == ActorType::Pet ||
720              mType == ActorType::Homunculus ||
721              mType == ActorType::SkillUnit)
722     {
723         if (attacker == localPlayer)
724         {
725             color = &userPalette->getColor(
726                 UserColorId::HIT_LOCAL_PLAYER_MONSTER,
727                 255U);
728         }
729         else
730         {
731             color = &userPalette->getColor(
732                 UserColorId::HIT_PLAYER_MONSTER,
733                 255U);
734         }
735     }
736     else if (mType == ActorType::Player &&
737              attacker != localPlayer &&
738              this == localPlayer)
739     {
740         // here player was attacked by other player. mark him as enemy.
741         color = &userPalette->getColor(UserColorId::HIT_PLAYER_PLAYER,
742             255U);
743         attacker->setEnemy(true);
744         attacker->updateColors();
745     }
746     else
747     {
748         color = &userPalette->getColor(UserColorId::HIT_MONSTER_PLAYER,
749             255U);
750     }
751 
752     if (chatWindow != nullptr && mShowBattleEvents)
753     {
754         if (this == localPlayer)
755         {
756             if (attacker->mType == ActorType::Player || (amount != 0))
757             {
758                 ChatWindow::battleChatLog(strprintf("%s : Hit you  -%d",
759                     attacker->getName().c_str(), amount),
760                     ChatMsgType::BY_OTHER,
761                     IgnoreRecord_false,
762                     TryRemoveColors_true);
763             }
764         }
765         else if (attacker == localPlayer && (amount != 0))
766         {
767             ChatWindow::battleChatLog(strprintf("%s : You hit %s -%d",
768                 attacker->mName.c_str(),
769                 mName.c_str(),
770                 amount),
771                 ChatMsgType::BY_PLAYER,
772                 IgnoreRecord_false,
773                 TryRemoveColors_true);
774         }
775     }
776     if (font != nullptr && particleEngine != nullptr)
777     {
778         const std::string damage = amount != 0 ? toString(amount) :
779             // TRANSLATORS: dodge or miss message in attacks
780             type == AttackType::FLEE ? _("dodge") : _("miss");
781         // Show damage number
782         particleEngine->addTextSplashEffect(damage,
783             mPixelX,
784             mPixelY - 16,
785             color,
786             font,
787             true);
788     }
789     BLOCK_END("Being::takeDamage1")
790     BLOCK_START("Being::takeDamage2")
791 
792     if (type != AttackType::SKILL)
793         attacker->updateHit(amount);
794 
795     if (amount > 0)
796     {
797         if ((localPlayer != nullptr) && localPlayer == this)
798             localPlayer->setLastHitFrom(attacker->mName);
799 
800         mDamageTaken += amount;
801         if (mInfo != nullptr)
802         {
803             playSfx(mInfo->getSound(ItemSoundEvent::HURT),
804                 this,
805                 false,
806                 mX,
807                 mY);
808 
809             if (!mInfo->isStaticMaxHP())
810             {
811                 if ((mHP == 0) && mInfo->getMaxHP() < mDamageTaken)
812                     mInfo->setMaxHP(mDamageTaken);
813             }
814         }
815         if ((mHP != 0) && isAlive())
816         {
817             mHP -= amount;
818             if (mHP < 0)
819                 mHP = 0;
820         }
821 
822         if (mType == ActorType::Monster)
823         {
824             updatePercentHP();
825             updateName();
826         }
827         else if (mType == ActorType::Player &&
828                  (socialWindow != nullptr) &&
829                  !mName.empty())
830         {
831             socialWindow->updateAvatar(mName);
832         }
833 
834         if (effectManager != nullptr)
835         {
836             const int hitEffectId = getHitEffect(attacker,
837                 type,
838                 attackId,
839                 level);
840             if (hitEffectId >= 0)
841                 effectManager->trigger(hitEffectId, this, 0);
842         }
843     }
844     else
845     {
846         if (effectManager != nullptr)
847         {
848             int hitEffectId = -1;
849             if (type == AttackType::SKILL)
850             {
851                 hitEffectId = getHitEffect(attacker,
852                     AttackType::SKILLMISS,
853                     attackId,
854                     level);
855             }
856             else
857             {
858                 hitEffectId = getHitEffect(attacker,
859                     AttackType::MISS,
860                     attackId,
861                     level);
862             }
863             if (hitEffectId >= 0)
864                 effectManager->trigger(hitEffectId, this, 0);
865         }
866     }
867     BLOCK_END("Being::takeDamage2")
868 }
869 
getHitEffect(const Being * restrict const attacker,const AttackTypeT type,const int attackId,const int level)870 int Being::getHitEffect(const Being *restrict const attacker,
871                         const AttackTypeT type,
872                         const int attackId,
873                         const int level)
874 {
875     if (effectManager == nullptr)
876         return 0;
877 
878     BLOCK_START("Being::getHitEffect")
879     // Init the particle effect path based on current
880     // weapon or default.
881     int hitEffectId = 0;
882     if (type == AttackType::SKILL || type == AttackType::SKILLMISS)
883     {
884         const SkillData *restrict const data =
885             skillDialog->getSkillDataByLevel(attackId, level);
886         if (data == nullptr)
887             return -1;
888         if (type == AttackType::SKILL)
889         {
890             hitEffectId = data->hitEffectId;
891             if (hitEffectId == -1)
892                 hitEffectId = paths.getIntValue("skillHitEffectId");
893         }
894         else
895         {
896             hitEffectId = data->missEffectId;
897             if (hitEffectId == -1)
898                 hitEffectId = paths.getIntValue("skillMissEffectId");
899         }
900     }
901     else
902     {
903         if (attacker != nullptr)
904         {
905             const ItemInfo *restrict const attackerWeapon
906                 = attacker->getEquippedWeapon();
907             if (attackerWeapon != nullptr &&
908                 attacker->getType() == ActorType::Player)
909             {
910                 if (type == AttackType::MISS)
911                     hitEffectId = attackerWeapon->getMissEffectId();
912                 else if (type != AttackType::CRITICAL)
913                     hitEffectId = attackerWeapon->getHitEffectId();
914                 else
915                     hitEffectId = attackerWeapon->getCriticalHitEffectId();
916             }
917             else if (attacker->getType() == ActorType::Monster)
918             {
919                 const BeingInfo *restrict const info = attacker->getInfo();
920                 if (info != nullptr)
921                 {
922                     const Attack *restrict const atk =
923                         info->getAttack(attackId);
924                     if (atk != nullptr)
925                     {
926                         if (type == AttackType::MISS)
927                             hitEffectId = atk->mMissEffectId;
928                         else if (type != AttackType::CRITICAL)
929                             hitEffectId = atk->mHitEffectId;
930                         else
931                             hitEffectId = atk->mCriticalHitEffectId;
932                     }
933                     else
934                     {
935                         hitEffectId = getDefaultEffectId(type);
936                     }
937                 }
938             }
939             else
940             {
941                 hitEffectId = getDefaultEffectId(type);
942             }
943         }
944         else
945         {
946             hitEffectId = getDefaultEffectId(type);
947         }
948     }
949     BLOCK_END("Being::getHitEffect")
950     return hitEffectId;
951 }
952 
getDefaultEffectId(const AttackTypeT & restrict type)953 int Being::getDefaultEffectId(const AttackTypeT &restrict type)
954 {
955     if (type == AttackType::MISS)
956         return paths.getIntValue("missEffectId");
957     else if (type != AttackType::CRITICAL)
958         return paths.getIntValue("hitEffectId");
959     else
960         return paths.getIntValue("criticalHitEffectId");
961 }
962 
handleAttack(Being * restrict const victim,const int damage,const int attackId)963 void Being::handleAttack(Being *restrict const victim,
964                          const int damage,
965                          const int attackId) restrict2
966 {
967     if ((victim == nullptr) || (mInfo == nullptr))
968         return;
969 
970     BLOCK_START("Being::handleAttack")
971 
972     if (this != localPlayer)
973         setAction(BeingAction::ATTACK, attackId);
974 
975     mLastAttackX = victim->mX;
976     mLastAttackY = victim->mY;
977 
978     if (mType == ActorType::Player && (mEquippedWeapon != nullptr))
979         fireMissile(victim, mEquippedWeapon->getMissileConst());
980     else if (mInfo->getAttack(attackId) != nullptr)
981         fireMissile(victim, mInfo->getAttack(attackId)->mMissile);
982 
983     reset();
984     mActionTime = tick_time;
985 
986     if (Net::getNetworkType() == ServerType::TMWATHENA &&
987         this != localPlayer)
988     {
989         const uint8_t dir = calcDirection(victim->mX,
990             victim->mY);
991         if (dir != 0U)
992             setDirection(dir);
993     }
994 
995     if ((damage != 0) && victim->mType == ActorType::Player
996         && victim->mAction == BeingAction::SIT)
997     {
998         victim->setAction(BeingAction::STAND, 0);
999     }
1000 
1001     if (mType == ActorType::Player)
1002     {
1003         if (mSlots.size() >= 10)
1004         {
1005             // here 10 is weapon slot
1006             int weaponId = mSlots[10].spriteId;
1007             if (weaponId == 0)
1008                 weaponId = -100 - toInt(mSubType, int);
1009             const ItemInfo &info = ItemDB::get(weaponId);
1010             playSfx(info.getSound(
1011                 (damage > 0) ? ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1012                 victim,
1013                 true,
1014                 mX, mY);
1015         }
1016     }
1017     else
1018     {
1019         playSfx(mInfo->getSound((damage > 0) ?
1020             ItemSoundEvent::HIT : ItemSoundEvent::MISS), victim, true, mX, mY);
1021     }
1022     BLOCK_END("Being::handleAttack")
1023 }
1024 
handleSkillCasting(Being * restrict const victim,const int skillId,const int skillLevel)1025 void Being::handleSkillCasting(Being *restrict const victim,
1026                                const int skillId,
1027                                const int skillLevel) restrict2
1028 {
1029     if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1030         return;
1031 
1032     setAction(BeingAction::CAST, skillId);
1033 
1034     const SkillData *restrict const data = skillDialog->getSkillDataByLevel(
1035         skillId,
1036         skillLevel);
1037 
1038     if (data != nullptr)
1039     {
1040         effectManager->triggerDefault(data->castingSrcEffectId,
1041             this,
1042             paths.getIntValue("skillCastingSrcEffectId"));
1043         effectManager->triggerDefault(data->castingDstEffectId,
1044             victim,
1045             paths.getIntValue("skillCastingDstEffectId"));
1046         fireMissile(victim, data->castingMissile);
1047     }
1048 }
1049 
handleSkill(Being * restrict const victim,const int damage,const int skillId,const int skillLevel)1050 void Being::handleSkill(Being *restrict const victim,
1051                         const int damage,
1052                         const int skillId,
1053                         const int skillLevel) restrict2
1054 {
1055     if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1056         return;
1057 
1058     const SkillInfo *restrict const skill = skillDialog->getSkill(skillId);
1059     const SkillData *restrict const data = skill != nullptr
1060         ? skill->getData1(skillLevel) : nullptr;
1061     if (data != nullptr)
1062     {
1063         effectManager->triggerDefault(data->srcEffectId,
1064             this,
1065             paths.getIntValue("skillSrcEffectId"));
1066         effectManager->triggerDefault(data->dstEffectId,
1067             victim,
1068             paths.getIntValue("skillDstEffectId"));
1069         fireMissile(victim, data->missile);
1070     }
1071 
1072     if (this != localPlayer && (skill != nullptr))
1073     {
1074         const SkillType::SkillType type = skill->type;
1075         if ((type & SkillType::Attack) != 0 ||
1076             (type & SkillType::Ground) != 0)
1077         {
1078             setAction(BeingAction::ATTACK, 1);
1079         }
1080         else
1081         {
1082             setAction(BeingAction::STAND, 1);
1083         }
1084     }
1085 
1086     reset();
1087     mActionTime = tick_time;
1088 
1089     if (Net::getNetworkType() == ServerType::TMWATHENA &&
1090         this != localPlayer)
1091     {
1092         const uint8_t dir = calcDirection(victim->mX,
1093             victim->mY);
1094         if (dir != 0U)
1095             setDirection(dir);
1096     }
1097     if ((damage != 0) && victim->mType == ActorType::Player
1098         && victim->mAction == BeingAction::SIT)
1099     {
1100         victim->setAction(BeingAction::STAND, 0);
1101     }
1102     if (data != nullptr)
1103     {
1104         if (damage > 0)
1105             playSfx(data->soundHit, victim, true, mX, mY);
1106         else
1107             playSfx(data->soundMiss, victim, true, mX, mY);
1108     }
1109     else
1110     {
1111         playSfx(mInfo->getSound((damage > 0) ?
1112             ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1113             victim,
1114             true,
1115             mX, mY);
1116     }
1117 }
1118 
showNameBadge(const bool show)1119 void Being::showNameBadge(const bool show) restrict2
1120 {
1121     delete2(mBadges[BadgeIndex::Name])
1122     if (show &&
1123         !mName.empty() &&
1124         mShowBadges != BadgeDrawType::Hide)
1125     {
1126         const std::string badge = BadgesDB::getNameBadge(mName);
1127         if (!badge.empty())
1128         {
1129             mBadges[BadgeIndex::Name] = AnimatedSprite::load(
1130                 paths.getStringValue("badges") + badge,
1131                 0);
1132         }
1133     }
1134 }
1135 
setName(const std::string & restrict name)1136 void Being::setName(const std::string &restrict name) restrict2
1137 {
1138     mExtName = name;
1139     if (mType == ActorType::Npc)
1140     {
1141         mName = name.substr(0, name.find('#', 0));
1142         showName();
1143     }
1144     else if (mType == ActorType::Player)
1145     {
1146         if (mName != name)
1147         {
1148             mName = name;
1149             showNameBadge(!mName.empty());
1150         }
1151         if (getShowName())
1152             showName();
1153     }
1154     else
1155     {
1156         if (mType == ActorType::Portal)
1157             mName = name.substr(0, name.find('#', 0));
1158         else
1159             mName = name;
1160 
1161         if (getShowName())
1162             showName();
1163     }
1164 }
1165 
setShowName(const bool doShowName)1166 void Being::setShowName(const bool doShowName) restrict2
1167 {
1168     if (mShowName == doShowName)
1169         return;
1170 
1171     mShowName = doShowName;
1172 
1173     if (doShowName)
1174         showName();
1175     else
1176         delete2(mDispName)
1177 }
1178 
showGuildBadge(const bool show)1179 void Being::showGuildBadge(const bool show) restrict2
1180 {
1181     delete2(mBadges[BadgeIndex::Guild])
1182     if (show &&
1183         !mGuildName.empty() &&
1184         mShowBadges != BadgeDrawType::Hide)
1185     {
1186         const std::string badge = BadgesDB::getGuildBadge(mGuildName);
1187         if (!badge.empty())
1188         {
1189             mBadges[BadgeIndex::Guild] = AnimatedSprite::load(
1190                 paths.getStringValue("badges") + badge,
1191                 0);
1192         }
1193     }
1194 }
1195 
setGuildName(const std::string & restrict name)1196 void Being::setGuildName(const std::string &restrict name) restrict2
1197 {
1198     if (mGuildName != name)
1199     {
1200         mGuildName = name;
1201         showGuildBadge(!mGuildName.empty());
1202         updateBadgesCount();
1203     }
1204 }
1205 
showClanBadge(const bool show)1206 void Being::showClanBadge(const bool show) restrict2
1207 {
1208     delete2(mBadges[BadgeIndex::Clan])
1209     if (show &&
1210         !mClanName.empty() &&
1211         mShowBadges != BadgeDrawType::Hide)
1212     {
1213         const std::string badge = BadgesDB::getClanBadge(mClanName);
1214         if (!badge.empty())
1215         {
1216             mBadges[BadgeIndex::Clan] = AnimatedSprite::load(
1217                 paths.getStringValue("badges") + badge,
1218                 0);
1219         }
1220     }
1221 }
1222 
setClanName(const std::string & restrict name)1223 void Being::setClanName(const std::string &restrict name) restrict2
1224 {
1225     if (mClanName != name)
1226     {
1227         mClanName = name;
1228         showClanBadge(!mClanName.empty());
1229         updateBadgesCount();
1230     }
1231 }
1232 
setGuildPos(const std::string & restrict pos A_UNUSED)1233 void Being::setGuildPos(const std::string &restrict pos A_UNUSED) restrict2
1234 {
1235 }
1236 
addGuild(Guild * restrict const guild)1237 void Being::addGuild(Guild *restrict const guild) restrict2
1238 {
1239     if (guild == nullptr)
1240         return;
1241 
1242     mGuilds[guild->getId()] = guild;
1243 
1244     if (this == localPlayer && (socialWindow != nullptr))
1245         socialWindow->addTab(guild);
1246 }
1247 
removeGuild(const int id)1248 void Being::removeGuild(const int id) restrict2
1249 {
1250     if (this == localPlayer && (socialWindow != nullptr))
1251         socialWindow->removeTab(mGuilds[id]);
1252 
1253     if (mGuilds[id] != nullptr)
1254         mGuilds[id]->removeMember(mName);
1255     mGuilds.erase(id);
1256 }
1257 
getGuild(const std::string & restrict guildName) const1258 const Guild *Being::getGuild(const std::string &restrict guildName) const
1259                              restrict2
1260 {
1261     FOR_EACH (GuildsMapCIter, itr, mGuilds)
1262     {
1263         const Guild *restrict const guild = itr->second;
1264         if ((guild != nullptr) && guild->getName() == guildName)
1265             return guild;
1266     }
1267 
1268     return nullptr;
1269 }
1270 
getGuild(const int id) const1271 const Guild *Being::getGuild(const int id) const restrict2
1272 {
1273     const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
1274     if (itr != mGuilds.end())
1275         return itr->second;
1276 
1277     return nullptr;
1278 }
1279 
getGuild() const1280 Guild *Being::getGuild() const restrict2
1281 {
1282     const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
1283     if (itr != mGuilds.end())
1284         return itr->second;
1285 
1286     return nullptr;
1287 }
1288 
clearGuilds()1289 void Being::clearGuilds() restrict2
1290 {
1291     FOR_EACH (GuildsMapCIter, itr, mGuilds)
1292     {
1293         Guild *const guild = itr->second;
1294 
1295         if (guild != nullptr)
1296         {
1297             if (this == localPlayer && (socialWindow != nullptr))
1298                 socialWindow->removeTab(guild);
1299 
1300             guild->removeMember(mId);
1301         }
1302     }
1303 
1304     mGuilds.clear();
1305 }
1306 
setParty(Party * restrict const party)1307 void Being::setParty(Party *restrict const party) restrict2
1308 {
1309     if (party == mParty)
1310         return;
1311 
1312     Party *const old = mParty;
1313     mParty = party;
1314 
1315     if (old != nullptr)
1316         old->removeMember(mId);
1317 
1318     if (party != nullptr)
1319         party->addMember(mId, mName);
1320 
1321     updateColors();
1322 
1323     if (this == localPlayer && (socialWindow != nullptr))
1324     {
1325         if (old != nullptr)
1326             socialWindow->removeTab(old);
1327 
1328         if (party != nullptr)
1329             socialWindow->addTab(party);
1330     }
1331 }
1332 
updateGuild()1333 void Being::updateGuild() restrict2
1334 {
1335     if (localPlayer == nullptr)
1336         return;
1337 
1338     Guild *restrict const guild = localPlayer->getGuild();
1339     if (guild == nullptr)
1340     {
1341         clearGuilds();
1342         updateColors();
1343         return;
1344     }
1345     if (guild->getMember(mName) != nullptr)
1346     {
1347         setGuild(guild);
1348         if (!guild->getName().empty())
1349             setGuildName(guild->getName());
1350     }
1351     updateColors();
1352 }
1353 
setGuild(Guild * restrict const guild)1354 void Being::setGuild(Guild *restrict const guild) restrict2
1355 {
1356     Guild *restrict const old = getGuild();
1357     if (guild == old)
1358         return;
1359 
1360     clearGuilds();
1361     addGuild(guild);
1362 
1363     if (old != nullptr)
1364         old->removeMember(mName);
1365 
1366     updateColors();
1367 
1368     if (this == localPlayer && (socialWindow != nullptr))
1369     {
1370         if (old != nullptr)
1371             socialWindow->removeTab(old);
1372 
1373         if (guild != nullptr)
1374             socialWindow->addTab(guild);
1375     }
1376 }
1377 
fireMissile(Being * restrict const victim,const MissileInfo & restrict missile) const1378 void Being::fireMissile(Being *restrict const victim,
1379                         const MissileInfo &restrict missile) const restrict2
1380 {
1381     BLOCK_START("Being::fireMissile")
1382 
1383     if (victim == nullptr ||
1384         missile.particle.empty() ||
1385         particleEngine == nullptr)
1386     {
1387         BLOCK_END("Being::fireMissile")
1388         return;
1389     }
1390 
1391     Particle *restrict const target = particleEngine->createChild();
1392 
1393     if (target == nullptr)
1394     {
1395         BLOCK_END("Being::fireMissile")
1396         return;
1397     }
1398 
1399     // +++ add z particle position?
1400     Particle *restrict const missileParticle = target->addEffect(
1401         missile.particle,
1402         mPixelX,
1403         mPixelY,
1404         0);
1405 
1406     target->moveBy(Vector(0.0F, 0.0F, missile.z));
1407     target->setLifetime(missile.lifeTime);
1408     victim->controlAutoParticle(target);
1409 
1410     if (missileParticle != nullptr)
1411     {
1412         missileParticle->setDestination(target, missile.speed, 0.0F);
1413         missileParticle->setDieDistance(missile.dieDistance);
1414         missileParticle->setLifetime(missile.lifeTime);
1415     }
1416     BLOCK_END("Being::fireMissile")
1417 }
1418 
getSitAction() const1419 std::string Being::getSitAction() const restrict2
1420 {
1421     if (mHorseId != 0)
1422         return SpriteAction::SITRIDE;
1423     if (mMap != nullptr)
1424     {
1425         const unsigned char mask = mMap->getBlockMask(mX, mY);
1426         if ((mask & BlockMask::GROUNDTOP) != 0)
1427             return SpriteAction::SITTOP;
1428         else if ((mask & BlockMask::AIR) != 0)
1429             return SpriteAction::SITSKY;
1430         else if ((mask & BlockMask::WATER) != 0)
1431             return SpriteAction::SITWATER;
1432     }
1433     return SpriteAction::SIT;
1434 }
1435 
1436 
getMoveAction() const1437 std::string Being::getMoveAction() const restrict2
1438 {
1439     if (mHorseId != 0)
1440         return SpriteAction::RIDE;
1441     if (mMap != nullptr)
1442     {
1443         const unsigned char mask = mMap->getBlockMask(mX, mY);
1444         if ((mask & BlockMask::AIR) != 0)
1445             return SpriteAction::FLY;
1446         else if ((mask & BlockMask::WATER) != 0)
1447             return SpriteAction::SWIM;
1448     }
1449     return SpriteAction::MOVE;
1450 }
1451 
getWeaponAttackAction(const ItemInfo * restrict const weapon) const1452 std::string Being::getWeaponAttackAction(const ItemInfo *restrict const weapon)
1453                                          const restrict2
1454 {
1455     if (weapon == nullptr)
1456         return getAttackAction();
1457 
1458     if (mHorseId != 0)
1459         return weapon->getRideAttackAction();
1460     if (mMap != nullptr)
1461     {
1462         const unsigned char mask = mMap->getBlockMask(mX, mY);
1463         if ((mask & BlockMask::AIR) != 0)
1464             return weapon->getSkyAttackAction();
1465         else if ((mask & BlockMask::WATER) != 0)
1466             return weapon->getWaterAttackAction();
1467     }
1468     return weapon->getAttackAction();
1469 }
1470 
getAttackAction(const Attack * restrict const attack1) const1471 std::string Being::getAttackAction(const Attack *restrict const attack1) const
1472                                    restrict2
1473 {
1474     if (attack1 == nullptr)
1475         return getAttackAction();
1476 
1477     if (mHorseId != 0)
1478         return attack1->mRideAction;
1479     if (mMap != nullptr)
1480     {
1481         const unsigned char mask = mMap->getBlockMask(mX, mY);
1482         if ((mask & BlockMask::AIR) != 0)
1483             return attack1->mSkyAction;
1484         else if ((mask & BlockMask::WATER) != 0)
1485             return attack1->mWaterAction;
1486     }
1487     return attack1->mAction;
1488 }
1489 
getCastAction(const SkillInfo * restrict const skill) const1490 std::string Being::getCastAction(const SkillInfo *restrict const skill) const
1491                                  restrict2
1492 {
1493     if (skill == nullptr)
1494         return getCastAction();
1495 
1496     if (mHorseId != 0)
1497         return skill->castingRideAction;
1498     if (mMap != nullptr)
1499     {
1500         const unsigned char mask = mMap->getBlockMask(mX, mY);
1501         if ((mask & BlockMask::AIR) != 0)
1502             return skill->castingSkyAction;
1503         else if ((mask & BlockMask::WATER) != 0)
1504             return skill->castingWaterAction;
1505     }
1506     return skill->castingAction;
1507 }
1508 
1509 #define getSpriteAction(func, action) \
1510     std::string Being::get##func##Action() const restrict2\
1511 { \
1512     if (mHorseId != 0) \
1513         return SpriteAction::action##RIDE; \
1514     if (mMap) \
1515     { \
1516         const unsigned char mask = mMap->getBlockMask(mX, mY); \
1517         if (mask & BlockMask::AIR) \
1518             return SpriteAction::action##SKY; \
1519         else if (mask & BlockMask::WATER) \
1520             return SpriteAction::action##WATER; \
1521     } \
1522     return SpriteAction::action; \
1523 }
1524 
getSpriteAction(Attack,ATTACK)1525 getSpriteAction(Attack, ATTACK)
1526 getSpriteAction(Cast, CAST)
1527 getSpriteAction(Dead, DEAD)
1528 getSpriteAction(Spawn, SPAWN)
1529 
1530 std::string Being::getStandAction() const restrict2
1531 {
1532     if (mHorseId != 0)
1533         return SpriteAction::STANDRIDE;
1534     if (mMap != nullptr)
1535     {
1536         const unsigned char mask = mMap->getBlockMask(mX, mY);
1537         if (mTrickDead)
1538         {
1539             if ((mask & BlockMask::AIR) != 0)
1540                 return SpriteAction::DEADSKY;
1541             else if ((mask & BlockMask::WATER) != 0)
1542                 return SpriteAction::DEADWATER;
1543             else
1544                 return SpriteAction::DEAD;
1545         }
1546         if ((mask & BlockMask::AIR) != 0)
1547             return SpriteAction::STANDSKY;
1548         else if ((mask & BlockMask::WATER) != 0)
1549             return SpriteAction::STANDWATER;
1550     }
1551     return SpriteAction::STAND;
1552 }
1553 
setAction(const BeingActionT & restrict action,const int attackId)1554 void Being::setAction(const BeingActionT &restrict action,
1555                       const int attackId) restrict2
1556 {
1557     std::string currentAction = SpriteAction::INVALID;
1558 
1559     switch (action)
1560     {
1561         case BeingAction::MOVE:
1562             if (mInfo != nullptr)
1563             {
1564                 playSfx(mInfo->getSound(
1565                     ItemSoundEvent::MOVE), nullptr, true, mX, mY);
1566             }
1567             currentAction = getMoveAction();
1568             // Note: When adding a run action,
1569             // Differentiate walk and run with action name,
1570             // while using only the ACTION_MOVE.
1571             break;
1572         case BeingAction::SIT:
1573             currentAction = getSitAction();
1574             if (mInfo != nullptr)
1575             {
1576                 ItemSoundEvent::Type event;
1577                 if (currentAction == SpriteAction::SITTOP)
1578                     event = ItemSoundEvent::SITTOP;
1579                 else
1580                     event = ItemSoundEvent::SIT;
1581                 playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
1582             }
1583             break;
1584         case BeingAction::ATTACK:
1585             if (mEquippedWeapon != nullptr)
1586             {
1587                 currentAction = getWeaponAttackAction(mEquippedWeapon);
1588                 reset();
1589             }
1590             else
1591             {
1592                 if (mInfo == nullptr || mInfo->getAttack(attackId) == nullptr)
1593                     break;
1594 
1595                 currentAction = getAttackAction(mInfo->getAttack(attackId));
1596                 reset();
1597 
1598                 // attack particle effect
1599                 if (ParticleEngine::enabled && (effectManager != nullptr))
1600                 {
1601                     const int effectId = mInfo->getAttack(attackId)->mEffectId;
1602                     if (effectId >= 0)
1603                     {
1604                         effectManager->triggerDirection(effectId,
1605                             this,
1606                             mSpriteDirection);
1607                     }
1608                 }
1609             }
1610             break;
1611         case BeingAction::CAST:
1612             if (skillDialog != nullptr)
1613             {
1614                 const SkillInfo *restrict const info =
1615                     skillDialog->getSkill(attackId);
1616                 currentAction = getCastAction(info);
1617             }
1618             break;
1619         case BeingAction::HURT:
1620             if (mInfo != nullptr)
1621             {
1622                 playSfx(mInfo->getSound(ItemSoundEvent::HURT),
1623                     this, false, mX, mY);
1624             }
1625             break;
1626         case BeingAction::DEAD:
1627             currentAction = getDeadAction();
1628             if (mInfo != nullptr)
1629             {
1630                 playSfx(mInfo->getSound(ItemSoundEvent::DIE),
1631                     this,
1632                     false,
1633                     mX, mY);
1634                 if (mType == ActorType::Monster ||
1635                     mType == ActorType::Npc ||
1636                     mType == ActorType::SkillUnit)
1637                 {
1638                     mYDiff = mInfo->getDeadSortOffsetY();
1639                 }
1640             }
1641             break;
1642         case BeingAction::STAND:
1643             currentAction = getStandAction();
1644             break;
1645         case BeingAction::SPAWN:
1646             if (mInfo != nullptr)
1647             {
1648                 playSfx(mInfo->getSound(ItemSoundEvent::SPAWN),
1649                     nullptr, true, mX, mY);
1650             }
1651             currentAction = getSpawnAction();
1652             break;
1653         case BeingAction::PRESTAND:
1654             break;
1655         default:
1656             logger->log("Being::setAction unknown action: "
1657                 + toString(CAST_U32(action)));
1658             break;
1659     }
1660 
1661     if (currentAction != SpriteAction::INVALID)
1662     {
1663         mSpriteAction = currentAction;
1664         play(currentAction);
1665         if (mEmotionSprite != nullptr)
1666             mEmotionSprite->play(currentAction);
1667         if (mAnimationEffect != nullptr)
1668             mAnimationEffect->play(currentAction);
1669         for_each_badges()
1670         {
1671             AnimatedSprite *const sprite = mBadges[f];
1672             if (sprite != nullptr)
1673                 sprite->play(currentAction);
1674         }
1675         for_each_horses(mDownHorseSprites)
1676             (*it)->play(currentAction);
1677         for_each_horses(mUpHorseSprites)
1678             (*it)->play(currentAction);
1679         mAction = action;
1680     }
1681 
1682     if (currentAction != SpriteAction::MOVE
1683         && currentAction != SpriteAction::FLY
1684         && currentAction != SpriteAction::SWIM)
1685     {
1686         mActionTime = tick_time;
1687     }
1688 }
1689 
setDirection(const uint8_t direction)1690 void Being::setDirection(const uint8_t direction) restrict2
1691 {
1692     if (mDirection == direction)
1693         return;
1694 
1695     mDirection = direction;
1696 
1697     mDirectionDelayed = 0;
1698 
1699     // if the direction does not change much, keep the common component
1700     int mFaceDirection = mDirection & direction;
1701     if (mFaceDirection == 0)
1702         mFaceDirection = direction;
1703 
1704     SpriteDirection::Type dir;
1705     if ((mFaceDirection & BeingDirection::UP) != 0)
1706     {
1707         if ((mFaceDirection & BeingDirection::LEFT) != 0)
1708             dir = SpriteDirection::UPLEFT;
1709         else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1710             dir = SpriteDirection::UPRIGHT;
1711         else
1712             dir = SpriteDirection::UP;
1713     }
1714     else if ((mFaceDirection & BeingDirection::DOWN) != 0)
1715     {
1716         if ((mFaceDirection & BeingDirection::LEFT) != 0)
1717             dir = SpriteDirection::DOWNLEFT;
1718         else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1719             dir = SpriteDirection::DOWNRIGHT;
1720         else
1721             dir = SpriteDirection::DOWN;
1722     }
1723     else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1724     {
1725         dir = SpriteDirection::RIGHT;
1726     }
1727     else
1728     {
1729         dir = SpriteDirection::LEFT;
1730     }
1731     mSpriteDirection = dir;
1732 
1733     CompoundSprite::setSpriteDirection(dir);
1734     if (mEmotionSprite != nullptr)
1735         mEmotionSprite->setSpriteDirection(dir);
1736     if (mAnimationEffect != nullptr)
1737         mAnimationEffect->setSpriteDirection(dir);
1738 
1739     for_each_badges()
1740     {
1741         AnimatedSprite *const sprite = mBadges[f];
1742         if (sprite != nullptr)
1743             sprite->setSpriteDirection(dir);
1744     }
1745 
1746     for_each_horses(mDownHorseSprites)
1747         (*it)->setSpriteDirection(dir);
1748     for_each_horses(mUpHorseSprites)
1749         (*it)->setSpriteDirection(dir);
1750     recalcSpritesOrder();
1751 }
1752 
calcDirection() const1753 uint8_t Being::calcDirection() const restrict2
1754 {
1755     uint8_t dir = 0;
1756     if (mDest.x > mX)
1757         dir |= BeingDirection::RIGHT;
1758     else if (mDest.x < mX)
1759         dir |= BeingDirection::LEFT;
1760     if (mDest.y > mY)
1761         dir |= BeingDirection::DOWN;
1762     else if (mDest.y < mY)
1763         dir |= BeingDirection::UP;
1764     return dir;
1765 }
1766 
calcDirection(const int dstX,const int dstY) const1767 uint8_t Being::calcDirection(const int dstX,
1768                              const int dstY) const restrict2
1769 {
1770     uint8_t dir = 0;
1771     if (dstX > mX)
1772         dir |= BeingDirection::RIGHT;
1773     else if (dstX < mX)
1774         dir |= BeingDirection::LEFT;
1775     if (dstY > mY)
1776         dir |= BeingDirection::DOWN;
1777     else if (dstY < mY)
1778         dir |= BeingDirection::UP;
1779     return dir;
1780 }
1781 
nextTile()1782 void Being::nextTile() restrict2
1783 {
1784     if (mPath.empty())
1785     {
1786         mAction = BeingAction::PRESTAND;
1787         mPreStandTime = tick_time;
1788         return;
1789     }
1790 
1791     const Position pos = mPath.front();
1792     mPath.pop_front();
1793 
1794     const uint8_t dir = calcDirection(pos.x, pos.y);
1795     if (dir != 0U)
1796         setDirection(dir);
1797 
1798     if (mMap == nullptr ||
1799         !mMap->getWalk(pos.x, pos.y, getBlockWalkMask()))
1800     {
1801         setAction(BeingAction::STAND, 0);
1802         return;
1803     }
1804 
1805     mActionTime += mSpeed / 10;
1806     if ((mType != ActorType::Player || mUseDiagonal)
1807         && mX != pos.x && mY != pos.y)
1808     {
1809         mSpeed = mWalkSpeed * 14 / 10;
1810     }
1811     else
1812     {
1813         mSpeed = mWalkSpeed;
1814     }
1815 
1816     if (mX != pos.x || mY != pos.y)
1817     {
1818         mOldHeight = mMap->getHeightOffset(mX, mY);
1819         if (mReachable == Reachable::REACH_NO &&
1820             mMap->getBlockMask(mX, mY) != mMap->getBlockMask(pos.x, pos.y))
1821         {
1822             mReachable = Reachable::REACH_UNKNOWN;
1823         }
1824     }
1825     mX = pos.x;
1826     mY = pos.y;
1827     const uint8_t height = mMap->getHeightOffset(mX, mY);
1828     mPixelOffsetY = height - mOldHeight;
1829     mFixedOffsetY = height;
1830     mNeedPosUpdate = true;
1831     setAction(BeingAction::MOVE, 0);
1832 }
1833 
logic()1834 void Being::logic() restrict2
1835 {
1836     BLOCK_START("Being::logic")
1837     if (A_UNLIKELY(mSpeechTime != 0))
1838     {
1839         mSpeechTime--;
1840         if (mSpeechTime == 0 && mText != nullptr)
1841             delete2(mText)
1842     }
1843 
1844     if (A_UNLIKELY(mOwner != nullptr))
1845     {
1846         if (mType == ActorType::Homunculus ||
1847             mType == ActorType::Mercenary)
1848         {
1849             botLogic();
1850         }
1851     }
1852 
1853     const int time = tick_time * MILLISECONDS_IN_A_TICK;
1854     if (mEmotionSprite != nullptr)
1855         mEmotionSprite->update(time);
1856     for_each_horses(mDownHorseSprites)
1857         (*it)->update(time);
1858     for_each_horses(mUpHorseSprites)
1859         (*it)->update(time);
1860 
1861     if (A_UNLIKELY(mCastEndTime != 0 && mCastEndTime < tick_time))
1862     {
1863         mCastEndTime = 0;
1864         delete2(mCastingEffect)
1865     }
1866 
1867     if (A_UNLIKELY(mAnimationEffect))
1868     {
1869         mAnimationEffect->update(time);
1870         if (mAnimationEffect->isTerminated())
1871             delete2(mAnimationEffect)
1872     }
1873     if (A_UNLIKELY(mCastingEffect))
1874     {
1875         mCastingEffect->update(time);
1876         if (mCastingEffect->isTerminated())
1877             delete2(mCastingEffect)
1878     }
1879     for_each_badges()
1880     {
1881         AnimatedSprite *restrict const sprite = mBadges[f];
1882         if (sprite != nullptr)
1883             sprite->update(time);
1884     }
1885 
1886     int frameCount = CAST_S32(getFrameCount());
1887 
1888     switch (mAction)
1889     {
1890         case BeingAction::STAND:
1891         case BeingAction::SIT:
1892         case BeingAction::DEAD:
1893         case BeingAction::HURT:
1894         case BeingAction::SPAWN:
1895         case BeingAction::CAST:
1896         default:
1897             break;
1898 
1899         case BeingAction::MOVE:
1900         {
1901             if (get_elapsed_time(mActionTime) >= mSpeed)
1902                 nextTile();
1903             break;
1904         }
1905 
1906         case BeingAction::ATTACK:
1907         {
1908             if (mActionTime == 0)
1909                 break;
1910 
1911             int curFrame = 0;
1912             if (mAttackSpeed != 0)
1913             {
1914                 curFrame = (get_elapsed_time(mActionTime) * frameCount)
1915                     / mAttackSpeed;
1916             }
1917 
1918             if (this == localPlayer && curFrame >= frameCount)
1919                 nextTile();
1920 
1921             break;
1922         }
1923 
1924         case BeingAction::PRESTAND:
1925         {
1926             if (get_elapsed_time1(mPreStandTime) > 10)
1927                 setAction(BeingAction::STAND, 0);
1928             break;
1929         }
1930     }
1931 
1932     if (mAction == BeingAction::MOVE || mNeedPosUpdate)
1933     {
1934         const int xOffset = getOffset<BeingDirection::LEFT,
1935             BeingDirection::RIGHT>();
1936         const int yOffset = getOffset<BeingDirection::UP,
1937             BeingDirection::DOWN>();
1938         int offset = xOffset;
1939         if (offset == 0)
1940             offset = yOffset;
1941 
1942         if ((xOffset == 0) && (yOffset == 0))
1943             mNeedPosUpdate = false;
1944 
1945         const int halfTile = mapTileSize / 2;
1946         const float offset2 = static_cast<float>(
1947             mPixelOffsetY * abs(offset)) / 2;
1948 //        mSortOffsetY = (mOldHeight - mFixedOffsetY + mPixelOffsetY)
1949 //            * halfTile - offset2;
1950         mSortOffsetY = 0;
1951         const float yOffset3 = (mY + 1) * mapTileSize + yOffset
1952             - (mOldHeight + mPixelOffsetY) * halfTile + offset2;
1953 
1954         // Update pixel coordinates
1955         setPixelPositionF(static_cast<float>(mX * mapTileSize
1956             + mapTileSize / 2 + xOffset),
1957             yOffset3,
1958             0.0F);
1959     }
1960 
1961     if (A_UNLIKELY(mEmotionSprite))
1962     {
1963         mEmotionTime--;
1964         if (mEmotionTime == 0)
1965             delete2(mEmotionSprite)
1966     }
1967 
1968     ActorSprite::logic();
1969 
1970     if (frameCount < 10)
1971         frameCount = 10;
1972 
1973     if (A_UNLIKELY(!isAlive() &&
1974         mSpeed != 0 &&
1975         gameHandler->removeDeadBeings() &&
1976         get_elapsed_time(mActionTime) / mSpeed >= frameCount))
1977     {
1978         if (mType != ActorType::Player && (actorManager != nullptr))
1979             actorManager->destroy(this);
1980     }
1981 
1982     const SoundInfo *restrict const sound = mNextSound.sound;
1983     if (A_UNLIKELY(sound))
1984     {
1985         const int time2 = tick_time;
1986         if (time2 > mNextSound.time)
1987         {
1988             soundManager.playSfx(sound->sound,
1989                 mNextSound.x,
1990                 mNextSound.y);
1991             mNextSound.sound = nullptr;
1992             mNextSound.time = time2 + sound->delay;
1993         }
1994     }
1995 
1996     BLOCK_END("Being::logic")
1997 }
1998 
botLogic()1999 void Being::botLogic() restrict2
2000 {
2001     if ((mOwner == nullptr) || (mMap == nullptr) || (mInfo == nullptr))
2002         return;
2003 
2004     const int time = tick_time;
2005     const int thinkTime = mInfo->getThinkTime();
2006     if (abs(CAST_S32(mMoveTime) - time) < thinkTime)
2007         return;
2008 
2009     mMoveTime = time;
2010 
2011     int dstX = mOwner->mX;
2012     int dstY = mOwner->mY;
2013     const int warpDist = mInfo->getWarpDist();
2014     const int divX = abs(dstX - mX);
2015     const int divY = abs(dstY - mY);
2016 
2017     if (divX >= warpDist || divY >= warpDist)
2018     {
2019         if (mType == ActorType::Homunculus)
2020             homunculusHandler->moveToMaster();
2021         else
2022             mercenaryHandler->moveToMaster();
2023         mBotAi = true;
2024         return;
2025     }
2026     if (!mBotAi)
2027         return;
2028     if (mAction == BeingAction::MOVE)
2029     {
2030         if (mOwner->mAction == BeingAction::MOVE)
2031         {
2032             updateBotFollow(dstX, dstY,
2033                 divX, divY);
2034         }
2035         return;
2036     }
2037 
2038     switch (mOwner->mAction)
2039     {
2040         case BeingAction::MOVE:
2041         case BeingAction::PRESTAND:
2042             updateBotFollow(dstX, dstY,
2043                 divX, divY);
2044             break;
2045         case BeingAction::STAND:
2046         case BeingAction::SPAWN:
2047             botFixOffset(dstX, dstY);
2048             moveBotTo(dstX, dstY);
2049             break;
2050         case BeingAction::ATTACK:
2051         {
2052             const Being *const target = localPlayer->getTarget();
2053             if (target == nullptr)
2054                 return;
2055             const BeingId targetId = target->getId();
2056             if (mType == ActorType::Homunculus)
2057             {
2058                 homunculusHandler->attack(targetId,
2059                     Keep_true);
2060             }
2061             else
2062             {
2063                 mercenaryHandler->attack(targetId,
2064                     Keep_true);
2065             }
2066             break;
2067         }
2068         case BeingAction::SIT:
2069         case BeingAction::DEAD:
2070             botFixOffset(dstX, dstY);
2071             moveBotTo(dstX, dstY);
2072             break;
2073         case BeingAction::CAST:
2074         case BeingAction::HURT:
2075         default:
2076             break;
2077     }
2078 }
2079 
botFixOffset(int & restrict dstX,int & restrict dstY) const2080 void Being::botFixOffset(int &restrict dstX,
2081                          int &restrict dstY) const
2082 {
2083     if ((mInfo == nullptr) || (mOwner == nullptr))
2084         return;
2085 
2086     int offsetX1;
2087     int offsetY1;
2088     switch (mOwner->getCurrentAction())
2089     {
2090         case BeingAction::SIT:
2091             offsetX1 = mInfo->getSitOffsetX();
2092             offsetY1 = mInfo->getSitOffsetY();
2093             break;
2094 
2095         case BeingAction::MOVE:
2096             offsetX1 = mInfo->getMoveOffsetX();
2097             offsetY1 = mInfo->getMoveOffsetY();
2098             break;
2099 
2100         case BeingAction::DEAD:
2101             offsetX1 = mInfo->getDeadOffsetX();
2102             offsetY1 = mInfo->getDeadOffsetY();
2103             break;
2104 
2105         case BeingAction::ATTACK:
2106             offsetX1 = mInfo->getAttackOffsetX();
2107             offsetY1 = mInfo->getAttackOffsetY();
2108             break;
2109 
2110         case BeingAction::SPAWN:
2111         case BeingAction::HURT:
2112         case BeingAction::STAND:
2113         case BeingAction::PRESTAND:
2114         case BeingAction::CAST:
2115         default:
2116             offsetX1 = mInfo->getTargetOffsetX();
2117             offsetY1 = mInfo->getTargetOffsetY();
2118             break;
2119     }
2120 
2121     int offsetX = offsetX1;
2122     int offsetY = offsetY1;
2123     switch (mOwner->mDirection)
2124     {
2125         case BeingDirection::LEFT:
2126             offsetX = -offsetY1;
2127             offsetY = offsetX1;
2128             break;
2129         case BeingDirection::RIGHT:
2130             offsetX = offsetY1;
2131             offsetY = -offsetX1;
2132             break;
2133         case BeingDirection::UP:
2134             offsetY = -offsetY;
2135             offsetX = -offsetX;
2136             break;
2137         default:
2138         case BeingDirection::DOWN:
2139             break;
2140     }
2141     dstX += offsetX;
2142     dstY += offsetY;
2143     if (mMap != nullptr)
2144     {
2145         if (!mMap->getWalk(dstX, dstY, getBlockWalkMask()))
2146         {
2147             dstX = mOwner->mX;
2148             dstY = mOwner->mY;
2149         }
2150     }
2151 }
2152 
updateBotFollow(int dstX,int dstY,const int divX,const int divY)2153 void Being::updateBotFollow(int dstX,
2154                             int dstY,
2155                             const int divX,
2156                             const int divY)
2157 {
2158     const int followDist = mInfo->getStartFollowDist();
2159     const int dist = mInfo->getFollowDist();
2160     if (divX > followDist || divY > followDist)
2161     {
2162         if (dist > 0)
2163         {
2164             if (divX > followDist)
2165             {
2166                 if (dstX > mX + dist)
2167                     dstX -= dist;
2168                 else if (dstX + dist <= mX)
2169                     dstX += dist;
2170             }
2171             else
2172             {
2173                 dstX = mX;
2174             }
2175             if (divY > followDist)
2176             {
2177                 if (dstY > mY + dist)
2178                     dstY -= dist;
2179                 else if (dstX + dist <= mX)
2180                     dstY += dist;
2181             }
2182             else
2183             {
2184                 dstY = mY;
2185             }
2186         }
2187         botFixOffset(dstX, dstY);
2188         moveBotTo(dstX, dstY);
2189     }
2190 }
2191 
moveBotTo(int dstX,int dstY)2192 void Being::moveBotTo(int dstX,
2193                       int dstY)
2194 {
2195     const int dstX0 = mOwner->mX;
2196     const int dstY0 = mOwner->mY;
2197     const unsigned char blockWalkMask = getBlockWalkMask();
2198     if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2199     {
2200         if (dstX != dstX0)
2201         {
2202             dstX = dstX0;
2203             if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2204                 dstY = dstY0;
2205         }
2206         else if (dstY != dstY0)
2207         {
2208             dstY = dstY0;
2209             if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2210                 dstX = dstX0;
2211         }
2212     }
2213     if (mX != dstX || mY != dstY)
2214     {
2215         if (mType == ActorType::Homunculus)
2216             homunculusHandler->move(dstX, dstY);
2217         else
2218             mercenaryHandler->move(dstX, dstY);
2219         return;
2220     }
2221     updateBotDirection(dstX, dstY);
2222 }
2223 
updateBotDirection(const int dstX,const int dstY)2224 void Being::updateBotDirection(const int dstX,
2225                                const int dstY)
2226 {
2227     int directionType = 0;
2228     switch (mOwner->getCurrentAction())
2229     {
2230         case BeingAction::STAND:
2231         case BeingAction::MOVE:
2232         case BeingAction::HURT:
2233         case BeingAction::SPAWN:
2234         case BeingAction::CAST:
2235         case BeingAction::PRESTAND:
2236         default:
2237             directionType = mInfo->getDirectionType();
2238             break;
2239         case BeingAction::SIT:
2240             directionType = mInfo->getSitDirectionType();
2241             break;
2242         case BeingAction::DEAD:
2243             directionType = mInfo->getDeadDirectionType();
2244             break;
2245         case BeingAction::ATTACK:
2246             directionType = mInfo->getAttackDirectionType();
2247             break;
2248     }
2249 
2250     uint8_t newDir = 0;
2251     switch (directionType)
2252     {
2253         case 0:
2254         default:
2255             return;
2256 
2257         case 1:
2258             newDir = mOwner->mDirection;
2259             break;
2260 
2261         case 2:
2262         {
2263             const int dstX0 = mOwner->mX;
2264             const int dstY0 = mOwner->mY;
2265             if (dstX > dstX0)
2266                 newDir |= BeingDirection::LEFT;
2267             else if (dstX < dstX0)
2268                 newDir |= BeingDirection::RIGHT;
2269             if (dstY > dstY0)
2270                 newDir |= BeingDirection::UP;
2271             else if (dstY < dstY0)
2272                 newDir |= BeingDirection::DOWN;
2273             break;
2274         }
2275         case 3:
2276         {
2277             const int dstX0 = mOwner->mX;
2278             const int dstY0 = mOwner->mY;
2279             if (dstX > dstX0)
2280                 newDir |= BeingDirection::RIGHT;
2281             else if (dstX < dstX0)
2282                 newDir |= BeingDirection::LEFT;
2283             if (dstY > dstY0)
2284                 newDir |= BeingDirection::DOWN;
2285             else if (dstY < dstY0)
2286                 newDir |= BeingDirection::UP;
2287             break;
2288         }
2289         case 4:
2290         {
2291             const int dstX2 = mOwner->getLastAttackX();
2292             const int dstY2 = mOwner->getLastAttackY();
2293             if (dstX > dstX2)
2294                 newDir |= BeingDirection::LEFT;
2295             else if (dstX < dstX2)
2296                 newDir |= BeingDirection::RIGHT;
2297             if (dstY > dstY2)
2298                 newDir |= BeingDirection::UP;
2299             else if (dstY < dstY2)
2300                 newDir |= BeingDirection::DOWN;
2301             break;
2302         }
2303     }
2304     if ((newDir != 0U) && newDir != mDirection)
2305     {
2306         if (mType == ActorType::Homunculus)
2307             homunculusHandler->setDirection(newDir);
2308         else
2309             mercenaryHandler->setDirection(newDir);
2310     }
2311 }
2312 
updateBadgesPosition()2313 void Being::updateBadgesPosition()
2314 {
2315     const int px = mPixelX - mapTileSize / 2;
2316     const int py = mPixelY - mapTileSize * 2 - mapTileSize;
2317     if (mShowBadges != BadgeDrawType::Hide &&
2318         mBadgesCount != 0U)
2319     {
2320         if (mDispName != nullptr &&
2321             gui != nullptr)
2322         {
2323             if (mShowBadges == BadgeDrawType::Right)
2324             {
2325                 const Font *restrict const font = gui->getFont();
2326                 mBadgesX = mDispName->getX() + mDispName->getWidth();
2327                 mBadgesY = mDispName->getY() - font->getHeight();
2328             }
2329             else if (mShowBadges == BadgeDrawType::Bottom)
2330             {
2331                 mBadgesX = px + 8 - mBadgesCount * 8;
2332                 if (mVisibleNamePos == VisibleNamePos::Bottom)
2333                 {
2334                     mBadgesY = mDispName->getY();
2335                 }
2336                 else
2337                 {
2338                     mBadgesY = py + settings.playerNameOffset + 16;
2339                 }
2340             }
2341             else
2342             {
2343                 mBadgesX = px + 8 - mBadgesCount * 8;
2344                 if (mVisibleNamePos == VisibleNamePos::Top)
2345                     mBadgesY = py - mDispName->getHeight();
2346                 else
2347                     mBadgesY = py;
2348             }
2349         }
2350         else
2351         {
2352             if (mShowBadges == BadgeDrawType::Right)
2353             {
2354                 mBadgesX = px + settings.playerBadgeAtRightOffset;
2355                 mBadgesY = py;
2356             }
2357             else if (mShowBadges == BadgeDrawType::Bottom)
2358             {
2359                 mBadgesX = px + 8 - mBadgesCount * 8;
2360                 const int height = settings.playerNameOffset;
2361                 if (mVisibleNamePos == VisibleNamePos::Bottom)
2362                     mBadgesY = py + height;
2363                 else
2364                     mBadgesY = py + height + 16;
2365             }
2366             else
2367             {
2368                 mBadgesX = px + 8 - mBadgesCount * 8;
2369                 mBadgesY = py;
2370             }
2371         }
2372     }
2373 }
2374 
drawEmotion(Graphics * restrict const graphics,const int offsetX,const int offsetY) const2375 void Being::drawEmotion(Graphics *restrict const graphics,
2376                         const int offsetX,
2377                         const int offsetY) const restrict2
2378 {
2379     if (mErased)
2380         return;
2381 
2382     const int px = mPixelX - offsetX - mapTileSize / 2;
2383     const int py = mPixelY - offsetY - mapTileSize * 2 - mapTileSize;
2384     if (mAnimationEffect != nullptr)
2385         mAnimationEffect->draw(graphics, px, py);
2386     if (mShowBadges != BadgeDrawType::Hide &&
2387         mBadgesCount != 0U)
2388     {
2389         int x = mBadgesX - offsetX;
2390         const int y = mBadgesY - offsetY;
2391         for_each_badges()
2392         {
2393             const AnimatedSprite *restrict const sprite = mBadges[f];
2394             if (sprite != nullptr)
2395             {
2396                 sprite->draw(graphics, x, y);
2397                 x += 16;
2398             }
2399         }
2400     }
2401     if (mEmotionSprite != nullptr)
2402         mEmotionSprite->draw(graphics, px, py);
2403 }
2404 
drawSpeech(const int offsetX,const int offsetY)2405 void Being::drawSpeech(const int offsetX,
2406                        const int offsetY) restrict2
2407 {
2408     if (mErased)
2409         return;
2410     if (mSpeech.empty())
2411         return;
2412 
2413     const int px = mPixelX - offsetX;
2414     const int py = mPixelY - offsetY;
2415     const int speech = mSpeechType;
2416 
2417     // Draw speech above this being
2418     if (mSpeechTime == 0)
2419     {
2420         if (mSpeechBubble != nullptr &&
2421             mSpeechBubble->mVisible == Visible_true)
2422         {
2423             mSpeechBubble->setVisible(Visible_false);
2424         }
2425         mSpeech.clear();
2426     }
2427     else if (mSpeechTime > 0 && (speech == BeingSpeech::NAME_IN_BUBBLE ||
2428              speech == BeingSpeech::NO_NAME_IN_BUBBLE))
2429     {
2430         delete2(mText)
2431 
2432         if (mSpeechBubble != nullptr)
2433         {
2434             mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
2435                 py - getHeight() - (mSpeechBubble->getHeight()));
2436             mSpeechBubble->setVisible(Visible_true);
2437         }
2438     }
2439     else if (mSpeechTime > 0 && speech == BeingSpeech::TEXT_OVERHEAD)
2440     {
2441         if (mSpeechBubble != nullptr)
2442             mSpeechBubble->setVisible(Visible_false);
2443 
2444         if ((mText == nullptr) && (userPalette != nullptr))
2445         {
2446             mText = new Text(mSpeech,
2447                 mPixelX,
2448                 mPixelY - getHeight(),
2449                 Graphics::CENTER,
2450                 &theme->getColor(ThemeColorId::BUBBLE_TEXT, 255),
2451                 Speech_true,
2452                 nullptr);
2453             mText->adviseXY(mPixelX,
2454                 (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
2455                 mMoveNames);
2456         }
2457     }
2458     else if (speech == BeingSpeech::NO_SPEECH)
2459     {
2460         if (mSpeechBubble != nullptr)
2461             mSpeechBubble->setVisible(Visible_false);
2462         delete2(mText)
2463     }
2464 }
2465 
2466 template<signed char pos, signed char neg>
getOffset() const2467 int Being::getOffset() const restrict2
2468 {
2469     // Check whether we're walking in the requested direction
2470     if (mAction != BeingAction::MOVE || !(mDirection & (pos | neg)))
2471         return 0;
2472 
2473     int offset = 0;
2474 
2475     if (mMap && mSpeed)
2476     {
2477         const int time = get_elapsed_time(mActionTime);
2478         offset = (pos == BeingDirection::LEFT &&
2479             neg == BeingDirection::RIGHT) ?
2480             (time * mMap->getTileWidth() / mSpeed)
2481             : (time * mMap->getTileHeight() / mSpeed);
2482     }
2483 
2484     // We calculate the offset _from_ the _target_ location
2485     offset -= mapTileSize;
2486     if (offset > 0)
2487         offset = 0;
2488 
2489     // Going into negative direction? Invert the offset.
2490     if (mDirection & pos)
2491         offset = -offset;
2492 
2493     if (offset > mapTileSize)
2494         offset = mapTileSize;
2495     if (offset < -mapTileSize)
2496         offset = -mapTileSize;
2497 
2498     return offset;
2499 }
2500 
updateCoords()2501 void Being::updateCoords() restrict2
2502 {
2503     if (mDispName != nullptr)
2504     {
2505         int offsetX = mPixelX;
2506         int offsetY = mPixelY;
2507         if (mInfo != nullptr)
2508         {
2509             offsetX += mInfo->getNameOffsetX();
2510             offsetY += mInfo->getNameOffsetY();
2511         }
2512         // Monster names show above the sprite instead of below it
2513         if (mType == ActorType::Monster ||
2514             mVisibleNamePos == VisibleNamePos::Top)
2515         {
2516             offsetY += - settings.playerNameOffset - mDispName->getHeight();
2517         }
2518         mDispName->adviseXY(offsetX, offsetY, mMoveNames);
2519     }
2520     updateBadgesPosition();
2521 }
2522 
optionChanged(const std::string & restrict value)2523 void Being::optionChanged(const std::string &restrict value) restrict2
2524 {
2525     if (mType == ActorType::Player && value == "visiblenames")
2526     {
2527         setShowName(config.getIntValue("visiblenames") == VisibleName::Show);
2528         updateBadgesPosition();
2529     }
2530 }
2531 
flashName(const int time)2532 void Being::flashName(const int time) restrict2
2533 {
2534     if (mDispName != nullptr)
2535         mDispName->flash(time);
2536 }
2537 
getGenderSignWithSpace() const2538 std::string Being::getGenderSignWithSpace() const restrict2
2539 {
2540     const std::string &restrict str = getGenderSign();
2541     if (str.empty())
2542         return str;
2543     else
2544         return std::string(" ").append(str);
2545 }
2546 
getGenderSign() const2547 std::string Being::getGenderSign() const restrict2
2548 {
2549     std::string str;
2550     if (mShowGender)
2551     {
2552         if (getGender() == Gender::FEMALE)
2553             str = "\u2640";
2554         else if (getGender() == Gender::MALE)
2555             str = "\u2642";
2556         else if (mType == ActorType::Player)
2557             str = "\u2640";
2558     }
2559     if (mShowPlayersStatus &&
2560         mShowBadges == BadgeDrawType::Hide)
2561     {
2562         if (mShop)
2563             str.append("$");
2564         if (mAway)
2565         {
2566             // TRANSLATORS: this away status writed in player nick
2567             str.append(_("A"));
2568         }
2569         else if (mInactive)
2570         {
2571             // TRANSLATORS: this inactive status writed in player nick
2572             str.append(_("I"));
2573         }
2574     }
2575     return str;
2576 }
2577 
showName()2578 void Being::showName() restrict2
2579 {
2580     if (mName.empty())
2581         return;
2582 
2583     delete2(mDispName)
2584 
2585     if (mHideErased && playerRelations.getRelation(mName) == Relation::ERASED)
2586         return;
2587 
2588     std::string displayName(mName);
2589 
2590     if (mType != ActorType::Monster && (mShowGender || mShowLevel))
2591     {
2592         displayName.append(" ");
2593         if (mShowLevel && getLevel() != 0)
2594             displayName.append(toString(getLevel()));
2595 
2596         displayName.append(getGenderSign());
2597     }
2598 
2599     if (mType == ActorType::Monster)
2600     {
2601         if (config.getBoolValue("showMonstersTakedDamage"))
2602             displayName.append(", ").append(toString(getDamageTaken()));
2603     }
2604 
2605     Font *font = nullptr;
2606     if ((localPlayer != nullptr) && localPlayer->getTarget() == this
2607         && mType != ActorType::Monster)
2608     {
2609         font = boldFont;
2610     }
2611     else if (mType == ActorType::Player
2612              && !playerRelations.isGoodName(this) && (gui != nullptr))
2613     {
2614         font = gui->getSecureFont();
2615     }
2616 
2617     if (mInfo != nullptr)
2618     {
2619         mDispName = new FlashText(displayName,
2620             mPixelX + mInfo->getNameOffsetX(),
2621             mPixelY + mInfo->getNameOffsetY(),
2622             Graphics::CENTER,
2623             mNameColor,
2624             font);
2625     }
2626     else
2627     {
2628         mDispName = new FlashText(displayName,
2629             mPixelX,
2630             mPixelY,
2631             Graphics::CENTER,
2632             mNameColor,
2633             font);
2634     }
2635 
2636     updateCoords();
2637 }
2638 
setDefaultNameColor(const UserColorIdT defaultColor)2639 void Being::setDefaultNameColor(const UserColorIdT defaultColor) restrict2
2640 {
2641     switch (mTeamId)
2642     {
2643         case 0:
2644         default:
2645             mNameColor = &userPalette->getColor(defaultColor,
2646                 255U);
2647             break;
2648         case 1:
2649             mNameColor = &userPalette->getColor(UserColorId::TEAM1,
2650                 255U);
2651             break;
2652         case 2:
2653             mNameColor = &userPalette->getColor(UserColorId::TEAM2,
2654                 255U);
2655             break;
2656         case 3:
2657             mNameColor = &userPalette->getColor(UserColorId::TEAM3,
2658                 255U);
2659             break;
2660     }
2661 }
2662 
updateColors()2663 void Being::updateColors()
2664 {
2665     if (userPalette != nullptr)
2666     {
2667         if (mType == ActorType::Monster)
2668         {
2669             setDefaultNameColor(UserColorId::MONSTER);
2670             mTextColor = &userPalette->getColor(UserColorId::MONSTER,
2671                 255U);
2672         }
2673         else if (mType == ActorType::Npc)
2674         {
2675             setDefaultNameColor(UserColorId::NPC);
2676             mTextColor = &userPalette->getColor(UserColorId::NPC,
2677                 255U);
2678         }
2679         else if (mType == ActorType::Pet)
2680         {
2681             setDefaultNameColor(UserColorId::PET);
2682             mTextColor = &userPalette->getColor(UserColorId::PET,
2683                 255U);
2684         }
2685         else if (mType == ActorType::Homunculus)
2686         {
2687             setDefaultNameColor(UserColorId::HOMUNCULUS);
2688             mTextColor = &userPalette->getColor(UserColorId::HOMUNCULUS,
2689                 255U);
2690         }
2691         else if (mType == ActorType::SkillUnit)
2692         {
2693             setDefaultNameColor(UserColorId::SKILLUNIT);
2694             mTextColor = &userPalette->getColor(UserColorId::SKILLUNIT,
2695                 255U);
2696         }
2697         else if (this == localPlayer)
2698         {
2699             mNameColor = &userPalette->getColor(UserColorId::SELF, 255U);
2700             mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2701         }
2702         else
2703         {
2704             mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2705 
2706             if (playerRelations.getRelation(mName) != Relation::ERASED)
2707                 mErased = false;
2708             else
2709                 mErased = true;
2710 
2711             if (mIsGM)
2712             {
2713                 mTextColor = &userPalette->getColor(UserColorId::GM,
2714                     255U);
2715                 mNameColor = &userPalette->getColor(UserColorId::GM,
2716                     255U);
2717             }
2718             else if (mEnemy)
2719             {
2720                 mNameColor = &userPalette->getColor(UserColorId::ENEMY,
2721                 255U);
2722             }
2723             else if ((mParty != nullptr) && (localPlayer != nullptr)
2724                      && mParty == localPlayer->getParty())
2725             {
2726                 mNameColor = &userPalette->getColor(UserColorId::PARTY,
2727                     255U);
2728             }
2729             else if ((localPlayer != nullptr) && (getGuild() != nullptr)
2730                      && getGuild() == localPlayer->getGuild())
2731             {
2732                 mNameColor = &userPalette->getColor(UserColorId::GUILD,
2733                     255U);
2734             }
2735             else if (playerRelations.getRelation(mName) == Relation::FRIEND)
2736             {
2737                 mNameColor = &userPalette->getColor(UserColorId::FRIEND,
2738                     255U);
2739             }
2740             else if (playerRelations.getRelation(mName) ==
2741                      Relation::DISREGARDED
2742                      || playerRelations.getRelation(mName) ==
2743                      Relation::BLACKLISTED)
2744             {
2745                 mNameColor = &userPalette->getColor(UserColorId::DISREGARDED,
2746                     255U);
2747             }
2748             else if (playerRelations.getRelation(mName)
2749                      == Relation::IGNORED ||
2750                      playerRelations.getRelation(mName) == Relation::ENEMY2)
2751             {
2752                 mNameColor = &userPalette->getColor(UserColorId::IGNORED,
2753                     255U);
2754             }
2755             else if (playerRelations.getRelation(mName) == Relation::ERASED)
2756             {
2757                 mNameColor = &userPalette->getColor(UserColorId::ERASED,
2758                     255U);
2759             }
2760             else
2761             {
2762                 setDefaultNameColor(UserColorId::PC);
2763             }
2764         }
2765 
2766         if (mDispName != nullptr)
2767             mDispName->setColor(mNameColor);
2768     }
2769 }
2770 
updateSprite(const unsigned int slot,const int id,const std::string & restrict color)2771 void Being::updateSprite(const unsigned int slot,
2772                          const int id,
2773                          const std::string &restrict color) restrict2
2774 {
2775     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2776         return;
2777 
2778     if (slot >= CAST_U32(mSlots.size()))
2779         mSlots.resize(slot + 1, BeingSlot());
2780 
2781     if ((slot != 0U) && mSlots[slot].spriteId == id)
2782         return;
2783     setSpriteColor(slot,
2784         id,
2785         color);
2786 }
2787 
2788 // set sprite id, reset colors, reset cards
setSpriteId(const unsigned int slot,const int id)2789 void Being::setSpriteId(const unsigned int slot,
2790                         const int id) restrict2
2791 {
2792     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2793         return;
2794 
2795     if (slot >= CAST_U32(mSprites.size()))
2796         ensureSize(slot + 1);
2797 
2798     if (slot >= CAST_U32(mSlots.size()))
2799         mSlots.resize(slot + 1, BeingSlot());
2800 
2801     // id = 0 means unequip
2802     if (id == 0)
2803     {
2804         removeSprite(slot);
2805         mSpriteDraw[slot] = 0;
2806 
2807         const int id1 = mSlots[slot].spriteId;
2808         if (id1 != 0)
2809             removeItemParticles(id1);
2810     }
2811     else
2812     {
2813         const ItemInfo &info = ItemDB::get(id);
2814         const std::string &restrict filename = info.getSprite(
2815             mGender, mSubType);
2816         int lastTime = 0;
2817         int startTime = 0;
2818         AnimatedSprite *restrict equipmentSprite = nullptr;
2819 
2820         if (!filename.empty())
2821         {
2822             equipmentSprite = AnimatedSprite::delayedLoad(
2823                 pathJoin(paths.getStringValue("sprites"), filename),
2824                 0);
2825         }
2826 
2827         if (equipmentSprite != nullptr)
2828         {
2829             equipmentSprite->setSpriteDirection(getSpriteDirection());
2830             startTime = getStartTime();
2831             lastTime = getLastTime();
2832         }
2833 
2834         CompoundSprite::setSprite(slot, equipmentSprite);
2835         mSpriteDraw[slot] = id;
2836 
2837         addItemParticles(id, info.getDisplay());
2838 
2839         setAction(mAction, 0);
2840         if (equipmentSprite != nullptr)
2841         {
2842             if (lastTime > 0)
2843             {
2844                 equipmentSprite->setLastTime(startTime);
2845                 equipmentSprite->update(lastTime);
2846             }
2847         }
2848     }
2849 
2850     BeingSlot &beingSlot = mSlots[slot];
2851     beingSlot.spriteId = id;
2852     beingSlot.color.clear();
2853     beingSlot.colorId = ItemColor_one;
2854     beingSlot.cardsId = CardsList(nullptr);
2855     recalcSpritesOrder();
2856     if (beingEquipmentWindow != nullptr)
2857         beingEquipmentWindow->updateBeing(this);
2858 }
2859 
2860 // reset sprite id, reset colors, reset cards
unSetSprite(const unsigned int slot)2861 void Being::unSetSprite(const unsigned int slot) restrict2
2862 {
2863     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2864         return;
2865 
2866     if (slot >= CAST_U32(mSprites.size()))
2867         ensureSize(slot + 1);
2868 
2869     if (slot >= CAST_U32(mSlots.size()))
2870         mSlots.resize(slot + 1, BeingSlot());
2871 
2872     removeSprite(slot);
2873     mSpriteDraw[slot] = 0;
2874 
2875     BeingSlot &beingSlot = mSlots[slot];
2876     const int id1 = beingSlot.spriteId;
2877     if (id1 != 0)
2878         removeItemParticles(id1);
2879 
2880     beingSlot.spriteId = 0;
2881     beingSlot.color.clear();
2882     beingSlot.colorId = ItemColor_one;
2883     beingSlot.cardsId = CardsList(nullptr);
2884     recalcSpritesOrder();
2885     if (beingEquipmentWindow != nullptr)
2886         beingEquipmentWindow->updateBeing(this);
2887 }
2888 
2889 // set sprite id, use color string, reset cards
setSpriteColor(const unsigned int slot,const int id,const std::string & color)2890 void Being::setSpriteColor(const unsigned int slot,
2891                            const int id,
2892                            const std::string &color) restrict2
2893 {
2894     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2895         return;
2896 
2897     if (slot >= CAST_U32(mSprites.size()))
2898         ensureSize(slot + 1);
2899 
2900     if (slot >= CAST_U32(mSlots.size()))
2901         mSlots.resize(slot + 1, BeingSlot());
2902 
2903     // disabled for now, because it may broke replace/reorder sprites logic
2904 //    if (slot && mSlots[slot].spriteId == id)
2905 //        return;
2906 
2907     // id = 0 means unequip
2908     if (id == 0)
2909     {
2910         removeSprite(slot);
2911         mSpriteDraw[slot] = 0;
2912 
2913         const int id1 = mSlots[slot].spriteId;
2914         if (id1 != 0)
2915             removeItemParticles(id1);
2916     }
2917     else
2918     {
2919         const ItemInfo &info = ItemDB::get(id);
2920         const std::string &restrict filename = info.getSprite(
2921             mGender, mSubType);
2922         int lastTime = 0;
2923         int startTime = 0;
2924         AnimatedSprite *restrict equipmentSprite = nullptr;
2925 
2926         if (!filename.empty())
2927         {
2928             equipmentSprite = AnimatedSprite::delayedLoad(
2929                 pathJoin(paths.getStringValue("sprites"),
2930                 combineDye(filename, color)),
2931                 0);
2932         }
2933 
2934         if (equipmentSprite != nullptr)
2935         {
2936             equipmentSprite->setSpriteDirection(getSpriteDirection());
2937             startTime = getStartTime();
2938             lastTime = getLastTime();
2939         }
2940 
2941         CompoundSprite::setSprite(slot, equipmentSprite);
2942         mSpriteDraw[slot] = id;
2943         addItemParticles(id, info.getDisplay());
2944 
2945         setAction(mAction, 0);
2946         if (equipmentSprite != nullptr)
2947         {
2948             if (lastTime > 0)
2949             {
2950                 equipmentSprite->setLastTime(startTime);
2951                 equipmentSprite->update(lastTime);
2952             }
2953         }
2954     }
2955 
2956     BeingSlot &beingSlot = mSlots[slot];
2957     beingSlot.spriteId = id;
2958     beingSlot.color = color;
2959     beingSlot.colorId = ItemColor_one;
2960     beingSlot.cardsId = CardsList(nullptr);
2961     recalcSpritesOrder();
2962     if (beingEquipmentWindow != nullptr)
2963         beingEquipmentWindow->updateBeing(this);
2964 }
2965 
2966 // set sprite id, use color id, reset cards
setSpriteColorId(const unsigned int slot,const int id,ItemColor colorId)2967 void Being::setSpriteColorId(const unsigned int slot,
2968                              const int id,
2969                              ItemColor colorId) restrict2
2970 {
2971     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2972         return;
2973 
2974     if (slot >= CAST_U32(mSprites.size()))
2975         ensureSize(slot + 1);
2976 
2977     if (slot >= CAST_U32(mSlots.size()))
2978         mSlots.resize(slot + 1, BeingSlot());
2979 
2980     // disabled for now, because it may broke replace/reorder sprites logic
2981 //    if (slot && mSlots[slot].spriteId == id)
2982 //        return;
2983 
2984     std::string color;
2985 
2986     // id = 0 means unequip
2987     if (id == 0)
2988     {
2989         removeSprite(slot);
2990         mSpriteDraw[slot] = 0;
2991 
2992         const int id1 = mSlots[slot].spriteId;
2993         if (id1 != 0)
2994             removeItemParticles(id1);
2995     }
2996     else
2997     {
2998         const ItemInfo &info = ItemDB::get(id);
2999         const std::string &restrict filename = info.getSprite(
3000             mGender, mSubType);
3001         int lastTime = 0;
3002         int startTime = 0;
3003         AnimatedSprite *restrict equipmentSprite = nullptr;
3004 
3005         if (!filename.empty())
3006         {
3007             color = info.getDyeColorsString(colorId);
3008             equipmentSprite = AnimatedSprite::delayedLoad(
3009                 pathJoin(paths.getStringValue("sprites"),
3010                 combineDye(filename, color)),
3011                 0);
3012         }
3013 
3014         if (equipmentSprite != nullptr)
3015         {
3016             equipmentSprite->setSpriteDirection(getSpriteDirection());
3017             startTime = getStartTime();
3018             lastTime = getLastTime();
3019         }
3020 
3021         CompoundSprite::setSprite(slot, equipmentSprite);
3022         mSpriteDraw[slot] = id;
3023 
3024         addItemParticles(id, info.getDisplay());
3025 
3026         setAction(mAction, 0);
3027         if (equipmentSprite != nullptr)
3028         {
3029             if (lastTime > 0)
3030             {
3031                 equipmentSprite->setLastTime(startTime);
3032                 equipmentSprite->update(lastTime);
3033             }
3034         }
3035     }
3036 
3037     BeingSlot &beingSlot = mSlots[slot];
3038     beingSlot.spriteId = id;
3039     beingSlot.color = STD_MOVE(color);
3040     beingSlot.colorId = colorId;
3041     beingSlot.cardsId = CardsList(nullptr);
3042     recalcSpritesOrder();
3043     if (beingEquipmentWindow != nullptr)
3044         beingEquipmentWindow->updateBeing(this);
3045 }
3046 
3047 // set sprite id, colors from cards, cards
setSpriteCards(const unsigned int slot,const int id,const CardsList & cards)3048 void Being::setSpriteCards(const unsigned int slot,
3049                            const int id,
3050                            const CardsList &cards) restrict2
3051 {
3052     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3053         return;
3054 
3055     if (slot >= CAST_U32(mSprites.size()))
3056         ensureSize(slot + 1);
3057 
3058     if (slot >= CAST_U32(mSlots.size()))
3059         mSlots.resize(slot + 1, BeingSlot());
3060 
3061     // disabled for now, because it may broke replace/reorder sprites logic
3062 //    if (slot && mSlots[slot].spriteId == id)
3063 //        return;
3064 
3065     ItemColor colorId = ItemColor_one;
3066     std::string color;
3067 
3068     // id = 0 means unequip
3069     if (id == 0)
3070     {
3071         removeSprite(slot);
3072         mSpriteDraw[slot] = 0;
3073 
3074         const int id1 = mSlots[slot].spriteId;
3075         if (id1 != 0)
3076             removeItemParticles(id1);
3077     }
3078     else
3079     {
3080         const ItemInfo &info = ItemDB::get(id);
3081         const std::string &restrict filename = info.getSprite(
3082             mGender, mSubType);
3083         int lastTime = 0;
3084         int startTime = 0;
3085         AnimatedSprite *restrict equipmentSprite = nullptr;
3086 
3087         if (!cards.isEmpty())
3088             colorId = ItemColorManager::getColorFromCards(cards);
3089 
3090         if (!filename.empty())
3091         {
3092             color = info.getDyeColorsString(colorId);
3093 
3094             equipmentSprite = AnimatedSprite::delayedLoad(
3095                 pathJoin(paths.getStringValue("sprites"),
3096                 combineDye(filename, color)),
3097                 0);
3098         }
3099 
3100         if (equipmentSprite != nullptr)
3101         {
3102             equipmentSprite->setSpriteDirection(getSpriteDirection());
3103             startTime = getStartTime();
3104             lastTime = getLastTime();
3105         }
3106 
3107         CompoundSprite::setSprite(slot, equipmentSprite);
3108         mSpriteDraw[slot] = id;
3109 
3110         addItemParticlesCards(id,
3111             info.getDisplay(),
3112             cards);
3113 
3114         setAction(mAction, 0);
3115         if (equipmentSprite != nullptr)
3116         {
3117             if (lastTime > 0)
3118             {
3119                 equipmentSprite->setLastTime(startTime);
3120                 equipmentSprite->update(lastTime);
3121             }
3122         }
3123     }
3124 
3125     BeingSlot &beingSlot = mSlots[slot];
3126     beingSlot.spriteId = id;
3127     beingSlot.color = STD_MOVE(color);
3128     beingSlot.colorId = colorId;
3129     beingSlot.cardsId = CardsList(cards);
3130     recalcSpritesOrder();
3131     if (beingEquipmentWindow != nullptr)
3132         beingEquipmentWindow->updateBeing(this);
3133 }
3134 
setWeaponId(const int id)3135 void Being::setWeaponId(const int id) restrict2
3136 {
3137     if (id == 0)
3138         mEquippedWeapon = nullptr;
3139     else
3140         mEquippedWeapon = &ItemDB::get(id);
3141 }
3142 
setTempSprite(const unsigned int slot,const int id)3143 void Being::setTempSprite(const unsigned int slot,
3144                           const int id) restrict2
3145 {
3146     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3147         return;
3148 
3149     if (slot >= CAST_U32(mSprites.size()))
3150         ensureSize(slot + 1);
3151 
3152     if (slot >= CAST_U32(mSlots.size()))
3153         mSlots.resize(slot + 1, BeingSlot());
3154 
3155     BeingSlot &beingSlot = mSlots[slot];
3156 
3157     // id = 0 means unequip
3158     if (id == 0)
3159     {
3160         removeSprite(slot);
3161         mSpriteDraw[slot] = 0;
3162 
3163         const int id1 = beingSlot.spriteId;
3164         if (id1 != 0)
3165             removeItemParticles(id1);
3166     }
3167     else
3168     {
3169         const ItemInfo &info = ItemDB::get(id);
3170         const std::string &restrict filename = info.getSprite(
3171             mGender, mSubType);
3172         int lastTime = 0;
3173         int startTime = 0;
3174 
3175         AnimatedSprite *restrict equipmentSprite = nullptr;
3176 
3177         if (!filename.empty())
3178         {
3179             ItemColor colorId = ItemColor_one;
3180             const CardsList &cards = beingSlot.cardsId;
3181             if (!cards.isEmpty())
3182                 colorId = ItemColorManager::getColorFromCards(cards);
3183             std::string color = beingSlot.color;
3184             if (color.empty())
3185                 color = info.getDyeColorsString(colorId);
3186 
3187             equipmentSprite = AnimatedSprite::delayedLoad(
3188                 pathJoin(paths.getStringValue("sprites"),
3189                 combineDye(filename, color)),
3190                 0);
3191         }
3192 
3193         if (equipmentSprite != nullptr)
3194         {
3195             equipmentSprite->setSpriteDirection(getSpriteDirection());
3196             startTime = getStartTime();
3197             lastTime = getLastTime();
3198         }
3199 
3200         CompoundSprite::setSprite(slot, equipmentSprite);
3201         mSpriteDraw[slot] = id;
3202 
3203         // +++ here probably need use existing cards
3204         addItemParticles(id, info.getDisplay());
3205 
3206         setAction(mAction, 0);
3207         if (equipmentSprite != nullptr)
3208         {
3209             if (lastTime > 0)
3210             {
3211                 equipmentSprite->setLastTime(startTime);
3212                 equipmentSprite->update(lastTime);
3213             }
3214         }
3215     }
3216 }
3217 
setHairTempSprite(const unsigned int slot,const int id)3218 void Being::setHairTempSprite(const unsigned int slot,
3219                               const int id) restrict2
3220 {
3221     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3222         return;
3223 
3224     if (slot >= CAST_U32(mSprites.size()))
3225         ensureSize(slot + 1);
3226 
3227     if (slot >= CAST_U32(mSlots.size()))
3228         mSlots.resize(slot + 1, BeingSlot());
3229 
3230     const CardsList &cards = mSlots[slot].cardsId;
3231 
3232     // id = 0 means unequip
3233     if (id == 0)
3234     {
3235         removeSprite(slot);
3236         mSpriteDraw[slot] = 0;
3237 
3238         const int id1 = mSlots[slot].spriteId;
3239         if (id1 != 0)
3240             removeItemParticles(id1);
3241     }
3242     else
3243     {
3244         const ItemInfo &info = ItemDB::get(id);
3245         const std::string &restrict filename = info.getSprite(
3246             mGender, mSubType);
3247         int lastTime = 0;
3248         int startTime = 0;
3249         AnimatedSprite *restrict equipmentSprite = nullptr;
3250 
3251         if (!filename.empty())
3252         {
3253             ItemColor colorId = ItemColor_one;
3254             if (!cards.isEmpty())
3255                 colorId = ItemColorManager::getColorFromCards(cards);
3256 
3257             std::string color = info.getDyeColorsString(mHairColor);
3258             if (color.empty())
3259                 color = info.getDyeColorsString(colorId);
3260 
3261             equipmentSprite = AnimatedSprite::delayedLoad(
3262                 pathJoin(paths.getStringValue("sprites"),
3263                 combineDye(filename, color)),
3264                 0);
3265         }
3266 
3267         if (equipmentSprite != nullptr)
3268         {
3269             equipmentSprite->setSpriteDirection(getSpriteDirection());
3270             startTime = getStartTime();
3271             lastTime = getLastTime();
3272         }
3273 
3274         CompoundSprite::setSprite(slot, equipmentSprite);
3275         mSpriteDraw[slot] = id;
3276 
3277         addItemParticles(id, info.getDisplay());
3278 
3279         setAction(mAction, 0);
3280         if (equipmentSprite != nullptr)
3281         {
3282             if (lastTime > 0)
3283             {
3284                 equipmentSprite->setLastTime(startTime);
3285                 equipmentSprite->update(lastTime);
3286             }
3287         }
3288     }
3289 }
3290 
setHairColorSpriteID(const unsigned int slot,const int id)3291 void Being::setHairColorSpriteID(const unsigned int slot,
3292                                  const int id) restrict2
3293 {
3294     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3295         return;
3296 
3297     if (slot >= CAST_U32(mSprites.size()))
3298         ensureSize(slot + 1);
3299 
3300     if (slot >= CAST_U32(mSlots.size()))
3301         mSlots.resize(slot + 1, BeingSlot());
3302 
3303     BeingSlot &beingSlot = mSlots[slot];
3304     setSpriteColor(slot,
3305         id,
3306         beingSlot.color);
3307 }
3308 
setSpriteColor(const unsigned int slot,const std::string & restrict color)3309 void Being::setSpriteColor(const unsigned int slot,
3310                            const std::string &restrict color) restrict2
3311 {
3312     if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3313         return;
3314 
3315     if (slot >= CAST_U32(mSprites.size()))
3316         ensureSize(slot + 1);
3317 
3318     if (slot >= CAST_U32(mSlots.size()))
3319         mSlots.resize(slot + 1, BeingSlot());
3320 
3321     // disabled for now, because it may broke replace/reorder sprites logic
3322 //    if (slot && mSlots[slot].spriteId == id)
3323 //        return;
3324 
3325     BeingSlot &beingSlot = mSlots[slot];
3326     const int id = beingSlot.spriteId;
3327 
3328     // id = 0 means unequip
3329     if (id != 0)
3330     {
3331         const ItemInfo &info = ItemDB::get(id);
3332         const std::string &restrict filename = info.getSprite(
3333             mGender, mSubType);
3334         int lastTime = 0;
3335         int startTime = 0;
3336         AnimatedSprite *restrict equipmentSprite = nullptr;
3337 
3338         if (!filename.empty())
3339         {
3340             equipmentSprite = AnimatedSprite::delayedLoad(
3341                 pathJoin(paths.getStringValue("sprites"),
3342                 combineDye(filename, color)),
3343                 0);
3344         }
3345 
3346         if (equipmentSprite != nullptr)
3347         {
3348             equipmentSprite->setSpriteDirection(getSpriteDirection());
3349             startTime = getStartTime();
3350             lastTime = getLastTime();
3351         }
3352 
3353         CompoundSprite::setSprite(slot, equipmentSprite);
3354 
3355         setAction(mAction, 0);
3356         if (equipmentSprite != nullptr)
3357         {
3358             if (lastTime > 0)
3359             {
3360                 equipmentSprite->setLastTime(startTime);
3361                 equipmentSprite->update(lastTime);
3362             }
3363         }
3364     }
3365 
3366     beingSlot.color = color;
3367     beingSlot.colorId = ItemColor_one;
3368     if (beingEquipmentWindow != nullptr)
3369         beingEquipmentWindow->updateBeing(this);
3370 }
3371 
setHairStyle(const unsigned int slot,const int id)3372 void Being::setHairStyle(const unsigned int slot,
3373                          const int id) restrict2
3374 {
3375     if (id != 0)
3376     {
3377         setSpriteColor(slot,
3378             id,
3379             ItemDB::get(id).getDyeColorsString(mHairColor));
3380     }
3381     else
3382     {
3383         setSpriteColor(slot,
3384             0,
3385             std::string());
3386     }
3387 }
3388 
setHairColor(const unsigned int slot,const ItemColor color)3389 void Being::setHairColor(const unsigned int slot,
3390                          const ItemColor color) restrict2
3391 {
3392     mHairColor = color;
3393     BeingSlot &beingSlot = mSlots[slot];
3394     const int id = beingSlot.spriteId;
3395     if (id != 0)
3396     {
3397         setSpriteColor(slot,
3398             id,
3399             ItemDB::get(id).getDyeColorsString(color));
3400     }
3401 }
3402 
setSpriteSlot(const unsigned int slot,const BeingSlot & beingSlot)3403 void Being::setSpriteSlot(const unsigned int slot,
3404                           const BeingSlot &beingSlot)
3405 {
3406     mSlots[slot] = beingSlot;
3407 }
3408 
dumpSprites() const3409 void Being::dumpSprites() const restrict2
3410 {
3411     STD_VECTOR<BeingSlot>::const_iterator it1 = mSlots.begin();
3412     const STD_VECTOR<BeingSlot>::const_iterator it1_end = mSlots.end();
3413 
3414     logger->log("sprites");
3415     for (; it1 != it1_end;
3416          ++ it1)
3417     {
3418         logger->log("%d,%s,%d",
3419             (*it1).spriteId,
3420             (*it1).color.c_str(),
3421             toInt((*it1).colorId, int));
3422     }
3423 }
3424 
updateName()3425 void Being::updateName() restrict2
3426 {
3427     if (mShowName)
3428         showName();
3429 }
3430 
reReadConfig()3431 void Being::reReadConfig()
3432 {
3433     BLOCK_START("Being::reReadConfig")
3434     if (mUpdateConfigTime + 1 < cur_time)
3435     {
3436         mAwayEffect = paths.getIntValue("afkEffectId");
3437         mHighlightMapPortals = config.getBoolValue("highlightMapPortals");
3438         mConfLineLim = config.getIntValue("chatMaxCharLimit");
3439         mSpeechType = config.getIntValue("speech");
3440         mHighlightMonsterAttackRange =
3441             config.getBoolValue("highlightMonsterAttackRange");
3442         mLowTraffic = config.getBoolValue("lowTraffic");
3443         mDrawHotKeys = config.getBoolValue("drawHotKeys");
3444         mShowBattleEvents = config.getBoolValue("showBattleEvents");
3445         mShowMobHP = config.getBoolValue("showMobHP");
3446         mShowOwnHP = config.getBoolValue("showOwnHP");
3447         mShowGender = config.getBoolValue("showgender");
3448         mShowLevel = config.getBoolValue("showlevel");
3449         mShowPlayersStatus = config.getBoolValue("showPlayersStatus");
3450         mEnableReorderSprites = config.getBoolValue("enableReorderSprites");
3451         mHideErased = config.getBoolValue("hideErased");
3452         mMoveNames = fromBool(config.getBoolValue("moveNames"), Move);
3453         mUseDiagonal = config.getBoolValue("useDiagonalSpeed");
3454         mShowBadges = static_cast<BadgeDrawType::Type>(
3455             config.getIntValue("showBadges"));
3456         mVisibleNamePos = static_cast<VisibleNamePos::Type>(
3457             config.getIntValue("visiblenamespos"));
3458 
3459         mUpdateConfigTime = cur_time;
3460     }
3461     BLOCK_END("Being::reReadConfig")
3462 }
3463 
updateFromCache()3464 bool Being::updateFromCache() restrict2
3465 {
3466     const BeingCacheEntry *restrict const entry =
3467         Being::getCacheEntry(getId());
3468 
3469     if ((entry != nullptr) && entry->getTime() + 120 >= cur_time)
3470     {
3471         if (!entry->getName().empty())
3472             setName(entry->getName());
3473         setPartyName(entry->getPartyName());
3474         setGuildName(entry->getGuildName());
3475         setLevel(entry->getLevel());
3476         setPvpRank(entry->getPvpRank());
3477         setIp(entry->getIp());
3478         setTeamId(entry->getTeamId());
3479 
3480         mAdvanced = entry->isAdvanced();
3481         if (mAdvanced)
3482         {
3483             const int flags = entry->getFlags();
3484             if ((serverFeatures != nullptr) &&
3485                 Net::getNetworkType() == ServerType::TMWATHENA)
3486             {
3487                 mShop = ((flags & BeingFlag::SHOP) != 0);
3488             }
3489             mAway = ((flags & BeingFlag::AWAY) != 0);
3490             mInactive = ((flags & BeingFlag::INACTIVE) != 0);
3491             if (mShop || mAway || mInactive)
3492                 updateName();
3493         }
3494         else
3495         {
3496             if (Net::getNetworkType() == ServerType::TMWATHENA)
3497                 mShop = false;
3498             mAway = false;
3499             mInactive = false;
3500         }
3501 
3502         showShopBadge(mShop);
3503         showInactiveBadge(mInactive);
3504         showAwayBadge(mAway);
3505         updateAwayEffect();
3506         if (mType == ActorType::Player || (mTeamId != 0U))
3507             updateColors();
3508         return true;
3509     }
3510     return false;
3511 }
3512 
addToCache() const3513 void Being::addToCache() const restrict2
3514 {
3515     if (localPlayer == this)
3516         return;
3517 
3518     BeingCacheEntry *entry = Being::getCacheEntry(getId());
3519     if (entry == nullptr)
3520     {
3521         entry = new BeingCacheEntry(getId());
3522         beingInfoCache.push_front(entry);
3523 
3524         if (beingInfoCache.size() >= CACHE_SIZE)
3525         {
3526             delete beingInfoCache.back();
3527             beingInfoCache.pop_back();
3528         }
3529     }
3530     if (!mLowTraffic)
3531         return;
3532 
3533     entry->setName(mName);
3534     entry->setLevel(getLevel());
3535     entry->setPartyName(getPartyName());
3536     entry->setGuildName(getGuildName());
3537     entry->setTime(cur_time);
3538     entry->setPvpRank(getPvpRank());
3539     entry->setIp(getIp());
3540     entry->setAdvanced(isAdvanced());
3541     entry->setTeamId(getTeamId());
3542     if (isAdvanced())
3543     {
3544         int flags = 0;
3545         if (Net::getNetworkType() == ServerType::TMWATHENA && mShop)
3546             flags += BeingFlag::SHOP;
3547         if (mAway)
3548             flags += BeingFlag::AWAY;
3549         if (mInactive)
3550             flags += BeingFlag::INACTIVE;
3551         entry->setFlags(flags);
3552     }
3553     else
3554     {
3555         entry->setFlags(0);
3556     }
3557 }
3558 
getCacheEntry(const BeingId id)3559 BeingCacheEntry* Being::getCacheEntry(const BeingId id)
3560 {
3561     FOR_EACH (std::list<BeingCacheEntry*>::iterator, i, beingInfoCache)
3562     {
3563         if (*i == nullptr)
3564             continue;
3565 
3566         if (id == (*i)->getId())
3567         {
3568             // Raise priority: move it to front
3569             if ((*i)->getTime() + 120 < cur_time)
3570             {
3571                 beingInfoCache.splice(beingInfoCache.begin(),
3572                                       beingInfoCache, i);
3573             }
3574             return *i;
3575         }
3576     }
3577     return nullptr;
3578 }
3579 
3580 
setGender(const GenderT gender)3581 void Being::setGender(const GenderT gender) restrict2
3582 {
3583     if (charServerHandler == nullptr)
3584         return;
3585 
3586     if (gender != mGender)
3587     {
3588         mGender = gender;
3589 
3590         const unsigned int sz = CAST_U32(mSlots.size());
3591 
3592         if (sz > CAST_U32(mSprites.size()))
3593             ensureSize(sz);
3594 
3595         // Reload all subsprites
3596         for (unsigned int i = 0;
3597              i < sz;
3598              i++)
3599         {
3600             BeingSlot &beingSlot = mSlots[i];
3601             const int id = beingSlot.spriteId;
3602             if (id != 0)
3603             {
3604                 const ItemInfo &info = ItemDB::get(id);
3605                 const std::string &restrict filename = info.getSprite(
3606                     mGender, mSubType);
3607                 int lastTime = 0;
3608                 int startTime = 0;
3609                 AnimatedSprite *restrict equipmentSprite = nullptr;
3610 
3611                 if (!filename.empty())
3612                 {
3613                     equipmentSprite = AnimatedSprite::delayedLoad(
3614                         pathJoin(paths.getStringValue("sprites"),
3615                         combineDye(filename, beingSlot.color)),
3616                         0);
3617                 }
3618 
3619                 if (equipmentSprite != nullptr)
3620                 {
3621                     equipmentSprite->setSpriteDirection(getSpriteDirection());
3622                     startTime = getStartTime();
3623                     lastTime = getLastTime();
3624                 }
3625 
3626                 CompoundSprite::setSprite(i, equipmentSprite);
3627                 setAction(mAction, 0);
3628                 if (equipmentSprite != nullptr)
3629                 {
3630                     if (lastTime > 0)
3631                     {
3632                         equipmentSprite->setLastTime(startTime);
3633                         equipmentSprite->update(lastTime);
3634                     }
3635                 }
3636 
3637                 if (beingEquipmentWindow != nullptr)
3638                     beingEquipmentWindow->updateBeing(this);
3639             }
3640         }
3641 
3642         updateName();
3643     }
3644 }
3645 
showGmBadge(const bool show)3646 void Being::showGmBadge(const bool show) restrict2
3647 {
3648     delete2(mBadges[BadgeIndex::Gm])
3649     if (show &&
3650         mIsGM &&
3651         mShowBadges != BadgeDrawType::Hide &&
3652         GroupDb::getShowBadge(mGroupId))
3653     {
3654         const std::string &gmBadge = GroupDb::getBadge(mGroupId);
3655         if (!gmBadge.empty())
3656         {
3657             mBadges[BadgeIndex::Gm] = AnimatedSprite::load(
3658                 paths.getStringValue("badges") + gmBadge,
3659                 0);
3660         }
3661     }
3662     updateBadgesCount();
3663     updateBadgesPosition();
3664 }
3665 
setGM(const bool gm)3666 void Being::setGM(const bool gm) restrict2
3667 {
3668     if (mIsGM != gm)
3669     {
3670         mIsGM = gm;
3671         updateColors();
3672     }
3673 }
3674 
talkTo() const3675 void Being::talkTo() const restrict2
3676 {
3677     if (npcHandler == nullptr)
3678         return;
3679 
3680     if (!PacketLimiter::limitPackets(PacketType::PACKET_NPC_TALK))
3681     {
3682         // using workaround...
3683         if ((playerHandler != nullptr) &&
3684             PacketLimiter::limitPackets(PacketType::PACKET_ATTACK))
3685         {
3686             playerHandler->attack(mId, Keep_false);
3687         }
3688         return;
3689     }
3690 
3691     npcHandler->talk(this);
3692 }
3693 
drawPlayer(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3694 void Being::drawPlayer(Graphics *restrict const graphics,
3695                        const int offsetX,
3696                        const int offsetY) const restrict2
3697 {
3698     if (!mErased)
3699     {
3700         // getActorX() + offsetX;
3701         const int px = mPixelX - mapTileSize / 2 + offsetX;
3702         // getActorY() + offsetY;
3703         const int py = mPixelY - mapTileSize + offsetY;
3704         if (mHorseInfo != nullptr)
3705         {
3706             HorseOffset &offset = mHorseInfo->offsets[mSpriteDirection];
3707             for_each_horses(mDownHorseSprites)
3708             {
3709                 (*it)->draw(graphics,
3710                     px + offset.downOffsetX,
3711                     py + offset.downOffsetY);
3712             }
3713 
3714             drawBeingCursor(graphics, px, py);
3715             drawPlayerSpriteAt(graphics,
3716                 px + offset.riderOffsetX,
3717                 py + offset.riderOffsetY);
3718 
3719             for_each_horses(mUpHorseSprites)
3720             {
3721                 (*it)->draw(graphics,
3722                     px + offset.upOffsetX,
3723                     py + offset.upOffsetY);
3724             }
3725         }
3726         else
3727         {
3728             drawBeingCursor(graphics, px, py);
3729             drawPlayerSpriteAt(graphics, px, py);
3730         }
3731     }
3732 }
3733 
drawBeingCursor(Graphics * const graphics,const int offsetX,const int offsetY) const3734 void Being::drawBeingCursor(Graphics *const graphics,
3735                             const int offsetX,
3736                             const int offsetY) const
3737 {
3738     if (mUsedTargetCursor != nullptr)
3739     {
3740         mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK);
3741         if (mInfo == nullptr)
3742         {
3743             mUsedTargetCursor->draw(graphics,
3744                 offsetX - mCursorPaddingX,
3745                 offsetY - mCursorPaddingY);
3746         }
3747         else
3748         {
3749             mUsedTargetCursor->draw(graphics,
3750                 offsetX + mInfo->getTargetOffsetX() - mCursorPaddingX,
3751                 offsetY + mInfo->getTargetOffsetY() - mCursorPaddingY);
3752         }
3753     }
3754 }
3755 
drawOther(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3756 void Being::drawOther(Graphics *restrict const graphics,
3757                       const int offsetX,
3758                       const int offsetY) const restrict2
3759 {
3760     // getActorX() + offsetX;
3761     const int px = mPixelX - mapTileSize / 2 + offsetX;
3762     // getActorY() + offsetY;
3763     const int py = mPixelY - mapTileSize + offsetY;
3764     drawBeingCursor(graphics, px, py);
3765     drawOtherSpriteAt(graphics, px, py);
3766 }
3767 
drawNpc(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3768 void Being::drawNpc(Graphics *restrict const graphics,
3769                     const int offsetX,
3770                     const int offsetY) const restrict2
3771 {
3772     // getActorX() + offsetX;
3773     const int px = mPixelX - mapTileSize / 2 + offsetX;
3774     // getActorY() + offsetY;
3775     const int py = mPixelY - mapTileSize + offsetY;
3776     drawBeingCursor(graphics, px, py);
3777     drawNpcSpriteAt(graphics, px, py);
3778 }
3779 
drawMonster(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3780 void Being::drawMonster(Graphics *restrict const graphics,
3781                         const int offsetX,
3782                         const int offsetY) const restrict2
3783 {
3784     // getActorX() + offsetX;
3785     const int px = mPixelX - mapTileSize / 2 + offsetX;
3786     // getActorY() + offsetY;
3787     const int py = mPixelY - mapTileSize + offsetY;
3788     drawBeingCursor(graphics, px, py);
3789     drawMonsterSpriteAt(graphics, px, py);
3790 }
3791 
drawHomunculus(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3792 void Being::drawHomunculus(Graphics *restrict const graphics,
3793                            const int offsetX,
3794                            const int offsetY) const restrict2
3795 {
3796     // getActorX() + offsetX;
3797     const int px = mPixelX - mapTileSize / 2 + offsetX;
3798     // getActorY() + offsetY;
3799     const int py = mPixelY - mapTileSize + offsetY;
3800     drawBeingCursor(graphics, px, py);
3801     drawHomunculusSpriteAt(graphics, px, py);
3802 }
3803 
drawMercenary(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3804 void Being::drawMercenary(Graphics *restrict const graphics,
3805                           const int offsetX,
3806                           const int offsetY) const restrict2
3807 {
3808     // getActorX() + offsetX;
3809     const int px = mPixelX - mapTileSize / 2 + offsetX;
3810     // getActorY() + offsetY;
3811     const int py = mPixelY - mapTileSize + offsetY;
3812     drawBeingCursor(graphics, px, py);
3813     drawMercenarySpriteAt(graphics, px, py);
3814 }
3815 
drawElemental(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3816 void Being::drawElemental(Graphics *restrict const graphics,
3817                           const int offsetX,
3818                           const int offsetY) const restrict2
3819 {
3820     // getActorX() + offsetX;
3821     const int px = mPixelX - mapTileSize / 2 + offsetX;
3822     // getActorY() + offsetY;
3823     const int py = mPixelY - mapTileSize + offsetY;
3824     drawBeingCursor(graphics, px, py);
3825     drawElementalSpriteAt(graphics, px, py);
3826 }
3827 
drawPortal(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3828 void Being::drawPortal(Graphics *restrict const graphics,
3829                        const int offsetX,
3830                        const int offsetY) const restrict2
3831 {
3832     // getActorX() + offsetX;
3833     const int px = mPixelX - mapTileSize / 2 + offsetX;
3834     // getActorY() + offsetY;
3835     const int py = mPixelY - mapTileSize + offsetY;
3836     drawPortalSpriteAt(graphics, px, py);
3837 }
3838 
draw(Graphics * restrict const graphics,const int offsetX,const int offsetY) const3839 void Being::draw(Graphics *restrict const graphics,
3840                  const int offsetX,
3841                  const int offsetY) const restrict2
3842 {
3843     switch (mType)
3844     {
3845         case ActorType::Player:
3846             drawPlayer(graphics,
3847                 offsetX,
3848                 offsetY);
3849             break;
3850         case ActorType::Portal:
3851             drawPortal(graphics,
3852                 offsetX,
3853                 offsetY);
3854             break;
3855         case ActorType::Homunculus:
3856             drawHomunculus(graphics,
3857                 offsetX,
3858                 offsetY);
3859             break;
3860         case ActorType::Mercenary:
3861             drawMercenary(graphics,
3862                 offsetX,
3863                 offsetY);
3864             break;
3865         case ActorType::Elemental:
3866             drawElemental(graphics,
3867                 offsetX,
3868                 offsetY);
3869             break;
3870         case ActorType::Monster:
3871             drawMonster(graphics,
3872                 offsetX,
3873                 offsetY);
3874             break;
3875         case ActorType::Npc:
3876             drawNpc(graphics,
3877                 offsetX,
3878                 offsetY);
3879             break;
3880         case ActorType::Pet:
3881         case ActorType::SkillUnit:
3882         case ActorType::Unknown:
3883         case ActorType::FloorItem:
3884         case ActorType::Avatar:
3885         default:
3886             drawOther(graphics,
3887                 offsetX,
3888                 offsetY);
3889             break;
3890     }
3891 }
3892 
drawPlayerSprites(Graphics * restrict const graphics,const int posX,const int posY) const3893 void Being::drawPlayerSprites(Graphics *restrict const graphics,
3894                               const int posX,
3895                               const int posY) const restrict2
3896 {
3897     const int sz = CompoundSprite::getNumberOfLayers();
3898     for (int f = 0; f < sz; f ++)
3899     {
3900         const int rSprite = mSpriteHide[mSpriteRemap[f]];
3901         if (rSprite == 1)
3902             continue;
3903 
3904         Sprite *restrict const sprite = mSprites[mSpriteRemap[f]];
3905         if (sprite != nullptr)
3906         {
3907             sprite->setAlpha(mAlpha);
3908             sprite->draw(graphics, posX, posY);
3909         }
3910     }
3911 }
3912 
drawSpritesSDL(Graphics * restrict const graphics,const int posX,const int posY) const3913 void Being::drawSpritesSDL(Graphics *restrict const graphics,
3914                            const int posX,
3915                            const int posY) const restrict2
3916 {
3917     const size_t sz = mSprites.size();
3918     for (size_t f = 0; f < sz; f ++)
3919     {
3920         const int rSprite = mSpriteHide[mSpriteRemap[f]];
3921         if (rSprite == 1)
3922             continue;
3923 
3924         const Sprite *restrict const sprite = mSprites[mSpriteRemap[f]];
3925         if (sprite != nullptr)
3926             sprite->draw(graphics, posX, posY);
3927     }
3928 }
3929 
drawBasic(Graphics * restrict const graphics,const int x,const int y) const3930 void Being::drawBasic(Graphics *restrict const graphics,
3931                       const int x,
3932                       const int y) const restrict2
3933 {
3934     drawCompound(graphics, x, y);
3935 }
3936 
drawCompound(Graphics * const graphics,const int posX,const int posY) const3937 void Being::drawCompound(Graphics *const graphics,
3938                          const int posX,
3939                          const int posY) const
3940 {
3941     FUNC_BLOCK("CompoundSprite::draw", 1)
3942     if (mNeedsRedraw)
3943         updateImages();
3944 
3945     if (mSprites.empty())  // Nothing to draw
3946         return;
3947 
3948     if (mAlpha == 1.0F && (mImage != nullptr))
3949     {
3950         graphics->drawImage(mImage,
3951             posX + mOffsetX,
3952             posY + mOffsetY);
3953     }
3954     else if ((mAlpha != 0.0F) && (mAlphaImage != nullptr))
3955     {
3956         mAlphaImage->setAlpha(mAlpha);
3957         graphics->drawImage(mAlphaImage,
3958             posX + mOffsetX,
3959             posY + mOffsetY);
3960     }
3961     else
3962     {
3963         Being::drawPlayerSprites(graphics, posX, posY);
3964     }
3965 }
3966 
drawPlayerSpriteAt(Graphics * restrict const graphics,const int x,const int y) const3967 void Being::drawPlayerSpriteAt(Graphics *restrict const graphics,
3968                                const int x,
3969                                const int y) const restrict2
3970 {
3971     drawCompound(graphics, x, y);
3972 
3973     if (mShowOwnHP &&
3974         (mInfo != nullptr) &&
3975         localPlayer == this &&
3976         mAction != BeingAction::DEAD)
3977     {
3978         drawHpBar(graphics,
3979             PlayerInfo::getAttribute(Attributes::PLAYER_MAX_HP),
3980             PlayerInfo::getAttribute(Attributes::PLAYER_HP),
3981             0,
3982             UserColorId::PLAYER_HP,
3983             UserColorId::PLAYER_HP2,
3984             x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
3985             y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
3986             2 * 50,
3987             4);
3988     }
3989 }
3990 
drawOtherSpriteAt(Graphics * restrict const graphics,const int x,const int y) const3991 void Being::drawOtherSpriteAt(Graphics *restrict const graphics,
3992                               const int x,
3993                               const int y) const restrict2
3994 {
3995     CompoundSprite::drawSimple(graphics, x, y);
3996 }
3997 
drawNpcSpriteAt(Graphics * restrict const graphics,const int x,const int y) const3998 void Being::drawNpcSpriteAt(Graphics *restrict const graphics,
3999                             const int x,
4000                             const int y) const restrict2
4001 {
4002     drawCompound(graphics, x, y);
4003 }
4004 
drawMonsterSpriteAt(Graphics * restrict const graphics,const int x,const int y) const4005 void Being::drawMonsterSpriteAt(Graphics *restrict const graphics,
4006                                 const int x,
4007                                 const int y) const restrict2
4008 {
4009     if (mHighlightMonsterAttackRange &&
4010         mType == ActorType::Monster &&
4011         mAction != BeingAction::DEAD)
4012     {
4013         if (userPalette == nullptr)
4014         {
4015             CompoundSprite::drawSimple(graphics, x, y);
4016             return;
4017         }
4018 
4019         int attackRange;
4020         if (mAttackRange != 0)
4021             attackRange = mapTileSize * mAttackRange;
4022         else
4023             attackRange = mapTileSize;
4024 
4025         graphics->setColor(userPalette->getColorWithAlpha(
4026             UserColorId::MONSTER_ATTACK_RANGE));
4027 
4028         graphics->fillRectangle(Rect(
4029             x - attackRange, y - attackRange,
4030             2 * attackRange + mapTileSize, 2 * attackRange + mapTileSize));
4031     }
4032 
4033     CompoundSprite::drawSimple(graphics, x, y);
4034 
4035     if (mShowMobHP &&
4036         (mInfo != nullptr) &&
4037         (localPlayer != nullptr) &&
4038         localPlayer->getTarget() == this &&
4039         mType == ActorType::Monster)
4040     {
4041         // show hp bar here
4042         int maxHP = mMaxHP;
4043         if (maxHP == 0)
4044             maxHP = mInfo->getMaxHP();
4045 
4046         drawHpBar(graphics,
4047                   maxHP,
4048                   mHP,
4049                   mDamageTaken,
4050                   UserColorId::MONSTER_HP,
4051                   UserColorId::MONSTER_HP2,
4052                   x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4053                   y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4054                   2 * 50,
4055                   4);
4056     }
4057 }
4058 
drawHomunculusSpriteAt(Graphics * restrict const graphics,const int x,const int y) const4059 void Being::drawHomunculusSpriteAt(Graphics *restrict const graphics,
4060                                    const int x,
4061                                    const int y) const restrict2
4062 {
4063     if (mHighlightMonsterAttackRange &&
4064         mAction != BeingAction::DEAD)
4065     {
4066         if (userPalette == nullptr)
4067         {
4068             CompoundSprite::drawSimple(graphics, x, y);
4069             return;
4070         }
4071 
4072         int attackRange;
4073         if (mAttackRange != 0)
4074             attackRange = mapTileSize * mAttackRange;
4075         else
4076             attackRange = mapTileSize;
4077 
4078         graphics->setColor(userPalette->getColorWithAlpha(
4079             UserColorId::MONSTER_ATTACK_RANGE));
4080 
4081         graphics->fillRectangle(Rect(
4082             x - attackRange, y - attackRange,
4083             2 * attackRange + mapTileSize, 2 * attackRange + mapTileSize));
4084     }
4085 
4086     CompoundSprite::drawSimple(graphics, x, y);
4087 
4088     if (mShowMobHP &&
4089         (mInfo != nullptr))
4090     {
4091         const HomunculusInfo *const info = PlayerInfo::getHomunculus();
4092         if ((info != nullptr) &&
4093             mId == info->id)
4094         {
4095             // show hp bar here
4096             int maxHP = PlayerInfo::getStatBase(Attributes::HOMUN_MAX_HP);
4097             if (maxHP == 0)
4098                 maxHP = mInfo->getMaxHP();
4099 
4100             drawHpBar(graphics,
4101                 maxHP,
4102                 PlayerInfo::getStatBase(Attributes::HOMUN_HP),
4103                 mDamageTaken,
4104                 UserColorId::HOMUN_HP,
4105                 UserColorId::HOMUN_HP2,
4106                 x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4107                 y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4108                 2 * 50,
4109                 4);
4110         }
4111     }
4112 }
4113 
drawMercenarySpriteAt(Graphics * restrict const graphics,const int x,const int y) const4114 void Being::drawMercenarySpriteAt(Graphics *restrict const graphics,
4115                                   const int x,
4116                                   const int y) const restrict2
4117 {
4118     if (mHighlightMonsterAttackRange &&
4119         mAction != BeingAction::DEAD)
4120     {
4121         if (userPalette == nullptr)
4122         {
4123             CompoundSprite::drawSimple(graphics, x, y);
4124             return;
4125         }
4126 
4127         int attackRange;
4128         if (mAttackRange != 0)
4129             attackRange = mapTileSize * mAttackRange;
4130         else
4131             attackRange = mapTileSize;
4132 
4133         graphics->setColor(userPalette->getColorWithAlpha(
4134             UserColorId::MONSTER_ATTACK_RANGE));
4135 
4136         graphics->fillRectangle(Rect(
4137             x - attackRange, y - attackRange,
4138             2 * attackRange + mapTileSize, 2 * attackRange + mapTileSize));
4139     }
4140 
4141     CompoundSprite::drawSimple(graphics, x, y);
4142 
4143     if (mShowMobHP &&
4144         (mInfo != nullptr))
4145     {
4146         const MercenaryInfo *const info = PlayerInfo::getMercenary();
4147         if ((info != nullptr) &&
4148             mId == info->id)
4149         {
4150             // show hp bar here
4151             int maxHP = PlayerInfo::getStatBase(Attributes::MERC_MAX_HP);
4152             if (maxHP == 0)
4153                 maxHP = mInfo->getMaxHP();
4154 
4155             drawHpBar(graphics,
4156                 maxHP,
4157                 PlayerInfo::getStatBase(Attributes::MERC_HP),
4158                 mDamageTaken,
4159                 UserColorId::MERC_HP,
4160                 UserColorId::MERC_HP2,
4161                 x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4162                 y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4163                 2 * 50,
4164                 4);
4165         }
4166     }
4167 }
4168 
drawElementalSpriteAt(Graphics * restrict const graphics,const int x,const int y) const4169 void Being::drawElementalSpriteAt(Graphics *restrict const graphics,
4170                                   const int x,
4171                                   const int y) const restrict2
4172 {
4173     if (mHighlightMonsterAttackRange &&
4174         mAction != BeingAction::DEAD)
4175     {
4176         if (userPalette == nullptr)
4177         {
4178             CompoundSprite::drawSimple(graphics, x, y);
4179             return;
4180         }
4181 
4182         int attackRange;
4183         if (mAttackRange != 0)
4184             attackRange = mapTileSize * mAttackRange;
4185         else
4186             attackRange = mapTileSize;
4187 
4188         graphics->setColor(userPalette->getColorWithAlpha(
4189             UserColorId::MONSTER_ATTACK_RANGE));
4190 
4191         graphics->fillRectangle(Rect(
4192             x - attackRange, y - attackRange,
4193             2 * attackRange + mapTileSize, 2 * attackRange + mapTileSize));
4194     }
4195 
4196     CompoundSprite::drawSimple(graphics, x, y);
4197 
4198     if (mShowMobHP &&
4199         (mInfo != nullptr))
4200     {
4201         if (mId == PlayerInfo::getElementalId())
4202         {
4203             // show hp bar here
4204             int maxHP = PlayerInfo::getStatBase(Attributes::ELEMENTAL_MAX_HP);
4205             if (maxHP == 0)
4206                 maxHP = mInfo->getMaxHP();
4207 
4208             drawHpBar(graphics,
4209                 maxHP,
4210                 PlayerInfo::getStatBase(Attributes::ELEMENTAL_HP),
4211                 mDamageTaken,
4212                 UserColorId::ELEMENTAL_HP,
4213                 UserColorId::ELEMENTAL_HP2,
4214                 x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4215                 y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4216                 2 * 50,
4217                 4);
4218         }
4219     }
4220 }
4221 
drawPortalSpriteAt(Graphics * restrict const graphics,const int x,const int y) const4222 void Being::drawPortalSpriteAt(Graphics *restrict const graphics,
4223                                const int x,
4224                                const int y) const restrict2
4225 {
4226     if (mHighlightMapPortals &&
4227         (mMap != nullptr) &&
4228         !mMap->getHasWarps())
4229     {
4230         if (userPalette == nullptr)
4231         {
4232             CompoundSprite::drawSimple(graphics, x, y);
4233             return;
4234         }
4235 
4236         graphics->setColor(userPalette->
4237             getColorWithAlpha(UserColorId::PORTAL_HIGHLIGHT));
4238 
4239         graphics->fillRectangle(Rect(x, y,
4240             mapTileSize, mapTileSize));
4241 
4242         if (mDrawHotKeys && !mName.empty())
4243         {
4244             const Color &color = userPalette->getColor(UserColorId::BEING,
4245                 255U);
4246             gui->getFont()->drawString(graphics, color, color, mName, x, y);
4247         }
4248     }
4249 
4250     CompoundSprite::drawSimple(graphics, x, y);
4251 }
4252 
drawHpBar(Graphics * restrict const graphics,const int maxHP,const int hp,const int damage,const UserColorIdT color1,const UserColorIdT color2,const int x,const int y,const int width,const int height) const4253 void Being::drawHpBar(Graphics *restrict const graphics,
4254                       const int maxHP,
4255                       const int hp,
4256                       const int damage,
4257                       const UserColorIdT color1,
4258                       const UserColorIdT color2,
4259                       const int x,
4260                       const int y,
4261                       const int width,
4262                       const int height) const restrict2
4263 {
4264     if (maxHP <= 0 || (userPalette == nullptr))
4265         return;
4266 
4267     float p;
4268 
4269     if (hp != 0)
4270     {
4271         p = static_cast<float>(maxHP) / static_cast<float>(hp);
4272     }
4273     else if (maxHP != damage)
4274     {
4275         p = static_cast<float>(maxHP)
4276             / static_cast<float>(maxHP - damage);
4277     }
4278     else
4279     {
4280         p = 1;
4281     }
4282 
4283     if (p <= 0 || p > width)
4284         return;
4285 
4286     const int dx = static_cast<int>(static_cast<float>(width) / p);
4287 
4288 #ifdef TMWA_SUPPORT
4289     if (!serverFeatures->haveServerHp())
4290     {   // old servers
4291         if ((damage == 0 && (this != localPlayer || hp == maxHP))
4292             || (hp == 0 && maxHP == damage))
4293         {
4294             graphics->setColor(userPalette->getColorWithAlpha(color1));
4295             graphics->fillRectangle(Rect(
4296                 x, y, dx, height));
4297             return;
4298         }
4299         else if (width - dx <= 0)
4300         {
4301             graphics->setColor(userPalette->getColorWithAlpha(color2));
4302             graphics->fillRectangle(Rect(
4303                 x, y, width, height));
4304             return;
4305         }
4306     }
4307     else
4308 #endif  // TMWA_SUPPORT
4309     {
4310         if (hp == maxHP)
4311         {
4312             graphics->setColor(userPalette->getColorWithAlpha(color1));
4313             graphics->fillRectangle(Rect(
4314                 x, y, dx, height));
4315             return;
4316         }
4317         else if (width - dx <= 0)
4318         {
4319             graphics->setColor(userPalette->getColorWithAlpha(color2));
4320             graphics->fillRectangle(Rect(
4321                 x, y, width, height));
4322             return;
4323         }
4324     }
4325 
4326     graphics->setColor(userPalette->getColorWithAlpha(color1));
4327     graphics->fillRectangle(Rect(
4328         x, y, dx, height));
4329 
4330     graphics->setColor(userPalette->getColorWithAlpha(color2));
4331     graphics->fillRectangle(Rect(x + dx, y, width - dx, height));
4332 }
4333 
setHP(const int hp)4334 void Being::setHP(const int hp) restrict2
4335 {
4336     mHP = hp;
4337     if (mMaxHP < mHP)
4338         mMaxHP = mHP;
4339     if (mType == ActorType::Monster)
4340         updatePercentHP();
4341 }
4342 
setMaxHP(const int hp)4343 void Being::setMaxHP(const int hp) restrict2
4344 {
4345     mMaxHP = hp;
4346     if (mMaxHP < mHP)
4347         mMaxHP = mHP;
4348 }
4349 
resetCounters()4350 void Being::resetCounters() restrict2
4351 {
4352     mMoveTime = 0;
4353     mAttackTime = 0;
4354     mTalkTime = 0;
4355     mOtherTime = 0;
4356     mTestTime = cur_time;
4357 }
4358 
recalcSpritesOrder()4359 void Being::recalcSpritesOrder() restrict2
4360 {
4361     if (!mEnableReorderSprites)
4362         return;
4363 
4364 //    logger->log("recalcSpritesOrder");
4365     const size_t sz = mSprites.size();
4366     if (sz < 1)
4367         return;
4368 
4369     STD_VECTOR<int> slotRemap;
4370     IntMap itemSlotRemap;
4371 
4372     STD_VECTOR<int>::iterator it;
4373     int oldHide[20];
4374     bool updatedSprite[20];
4375     int dir = mSpriteDirection;
4376     if (dir < 0 || dir >= 9)
4377         dir = 0;
4378     // hack for allow different logic in dead player
4379     if (mAction == BeingAction::DEAD)
4380         dir = 9;
4381 
4382     const unsigned int hairSlot = charServerHandler->hairSprite();
4383 
4384     for (size_t slot = sz; slot < 20; slot ++)
4385     {
4386         oldHide[slot] = 0;
4387         updatedSprite[slot] = false;
4388     }
4389 
4390     for (size_t slot = 0; slot < sz; slot ++)
4391     {
4392         oldHide[slot] = mSpriteHide[slot];
4393         mSpriteHide[slot] = 0;
4394         updatedSprite[slot] = false;
4395     }
4396 
4397     size_t spriteIdSize = mSlots.size();
4398     if (reportTrue(spriteIdSize > 20))
4399         spriteIdSize = 20;
4400 
4401     for (size_t slot = 0; slot < sz; slot ++)
4402     {
4403         slotRemap.push_back(CAST_S32(slot));
4404 
4405         if (spriteIdSize <= slot)
4406             continue;
4407 
4408         const int id = mSlots[slot].spriteId;
4409         if (id == 0)
4410             continue;
4411 
4412         const ItemInfo &info = ItemDB::get(id);
4413 
4414         if (info.isRemoveSprites())
4415         {
4416             const SpriteToItemMap *restrict const spriteToItems
4417                 = info.getSpriteToItemReplaceMap(dir);
4418 
4419             if (spriteToItems != nullptr)
4420             {
4421                 FOR_EACHP (SpriteToItemMapCIter, itr, spriteToItems)
4422                 {
4423                     const int remSlot = itr->first;
4424                     const IntMap &restrict itemReplacer = itr->second;
4425                     if (remSlot >= 0)
4426                     {   // slot known
4427                         if (CAST_U32(remSlot) >= spriteIdSize)
4428                             continue;
4429                         if (itemReplacer.empty())
4430                         {
4431                             mSpriteHide[remSlot] = 1;
4432                         }
4433                         else if (mSpriteHide[remSlot] != 1)
4434                         {
4435                             IntMapCIter repIt = itemReplacer.find(
4436                                 mSlots[remSlot].spriteId);
4437                             if (repIt == itemReplacer.end())
4438                             {
4439                                 repIt = itemReplacer.find(0);
4440                                 if (repIt == itemReplacer.end() ||
4441                                     repIt->second == 0)
4442                                 {
4443                                     repIt = itemReplacer.end();
4444                                 }
4445                             }
4446                             if (repIt != itemReplacer.end())
4447                             {
4448                                 mSpriteHide[remSlot] = repIt->second;
4449                                 if (repIt->second != 1)
4450                                 {
4451                                     if (CAST_U32(remSlot)
4452                                         != hairSlot)
4453                                     {
4454                                         setTempSprite(remSlot,
4455                                             repIt->second);
4456                                     }
4457                                     else
4458                                     {
4459                                         setHairTempSprite(remSlot,
4460                                             repIt->second);
4461                                     }
4462                                     updatedSprite[remSlot] = true;
4463                                 }
4464                             }
4465                         }
4466                     }
4467                     else
4468                     {   // slot unknown. Search for real slot, this can be slow
4469                         FOR_EACH (IntMapCIter, repIt, itemReplacer)
4470                         {
4471                             for (unsigned int slot2 = 0; slot2 < sz; slot2 ++)
4472                             {
4473                                 if (mSlots[slot2].spriteId == repIt->first)
4474                                 {
4475                                     mSpriteHide[slot2] = repIt->second;
4476                                     if (repIt->second != 1)
4477                                     {
4478                                         if (slot2 != hairSlot)
4479                                         {
4480                                             setTempSprite(slot2,
4481                                                 repIt->second);
4482                                         }
4483                                         else
4484                                         {
4485                                             setHairTempSprite(slot2,
4486                                                 repIt->second);
4487                                         }
4488                                         updatedSprite[slot2] = true;
4489                                     }
4490                                 }
4491                             }
4492                         }
4493                     }
4494                 }
4495             }
4496         }
4497 
4498         if (info.mDrawBefore[dir] > 0)
4499         {
4500             const int id2 = mSlots[info.mDrawBefore[dir]].spriteId;
4501             if (itemSlotRemap.find(id2) != itemSlotRemap.end())
4502             {
4503 //                logger->log("found duplicate (before)");
4504                 const ItemInfo &info2 = ItemDB::get(id2);
4505                 if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
4506                 {
4507 //                    logger->log("old more priority");
4508                     continue;
4509                 }
4510                 else
4511                 {
4512 //                    logger->log("new more priority");
4513                     itemSlotRemap.erase(id2);
4514                 }
4515             }
4516 
4517             itemSlotRemap[id] = -info.mDrawBefore[dir];
4518         }
4519         else if (info.mDrawAfter[dir] > 0)
4520         {
4521             const int id2 = mSlots[info.mDrawAfter[dir]].spriteId;
4522             if (itemSlotRemap.find(id2) != itemSlotRemap.end())
4523             {
4524                 const ItemInfo &info2 = ItemDB::get(id2);
4525                 if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
4526                 {
4527 //                    logger->log("old more priority");
4528                     continue;
4529                 }
4530                 else
4531                 {
4532 //                    logger->log("new more priority");
4533                     itemSlotRemap.erase(id2);
4534                 }
4535             }
4536 
4537             itemSlotRemap[id] = info.mDrawAfter[dir];
4538 //       logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]);
4539         }
4540     }
4541 //    logger->log("preparation end");
4542 
4543     int lastRemap = 0;
4544     unsigned cnt = 0;
4545 
4546     while (cnt < 15 && lastRemap >= 0)
4547     {
4548         lastRemap = -1;
4549         cnt ++;
4550 //        logger->log("iteration");
4551 
4552         for (unsigned int slot0 = 0; slot0 < sz; slot0 ++)
4553         {
4554             const int slot = searchSlotValue(slotRemap, slot0);
4555             const int val = slotRemap.at(slot);
4556             int id = 0;
4557 
4558             if (CAST_S32(spriteIdSize) > val)
4559                 id = mSlots[val].spriteId;
4560 
4561             int idx = -1;
4562             int idx1 = -1;
4563 //            logger->log("item %d, id=%d", slot, id);
4564             int reorder = 0;
4565             const IntMapCIter orderIt = itemSlotRemap.find(id);
4566             if (orderIt != itemSlotRemap.end())
4567                 reorder = orderIt->second;
4568 
4569             if (reorder < 0)
4570             {
4571 //                logger->log("move item %d before %d", slot, -reorder);
4572                 searchSlotValueItr(it, idx, slotRemap, -reorder);
4573                 if (it == slotRemap.end())
4574                     return;
4575                 searchSlotValueItr(it, idx1, slotRemap, val);
4576                 if (it == slotRemap.end())
4577                     return;
4578                 lastRemap = idx1;
4579                 if (idx1 + 1 != idx)
4580                 {
4581                     slotRemap.erase(it);
4582                     searchSlotValueItr(it, idx, slotRemap, -reorder);
4583                     slotRemap.insert(it, val);
4584                 }
4585             }
4586             else if (reorder > 0)
4587             {
4588 //                logger->log("move item %d after %d", slot, reorder);
4589                 searchSlotValueItr(it, idx, slotRemap, reorder);
4590                 searchSlotValueItr(it, idx1, slotRemap, val);
4591                 if (it == slotRemap.end())
4592                     return;
4593                 lastRemap = idx1;
4594                 if (idx1 != idx + 1)
4595                 {
4596                     slotRemap.erase(it);
4597                     searchSlotValueItr(it, idx, slotRemap, reorder);
4598                     if (it != slotRemap.end())
4599                     {
4600                         ++ it;
4601                         if (it != slotRemap.end())
4602                             slotRemap.insert(it, val);
4603                         else
4604                             slotRemap.push_back(val);
4605                     }
4606                     else
4607                     {
4608                         slotRemap.push_back(val);
4609                     }
4610                 }
4611             }
4612         }
4613     }
4614 
4615 //    logger->log("after remap");
4616     for (unsigned int slot = 0; slot < sz; slot ++)
4617     {
4618         mSpriteRemap[slot] = slotRemap[slot];
4619         if (mSpriteHide[slot] == 0)
4620         {
4621             if (oldHide[slot] != 0 && oldHide[slot] != 1)
4622             {
4623                 const BeingSlot &beingSlot = mSlots[slot];
4624                 const int id = beingSlot.spriteId;
4625                 if (id == 0)
4626                     continue;
4627 
4628                 updatedSprite[slot] = true;
4629                 setTempSprite(slot,
4630                     id);
4631             }
4632         }
4633     }
4634     for (size_t slot = 0; slot < spriteIdSize; slot ++)
4635     {
4636         if (mSpriteHide[slot] == 0)
4637         {
4638             const BeingSlot &beingSlot = mSlots[slot];
4639             const int id = beingSlot.spriteId;
4640             if (updatedSprite[slot] == false &&
4641                 mSpriteDraw[slot] != id)
4642             {
4643                 setTempSprite(static_cast<unsigned int>(slot),
4644                     id);
4645             }
4646         }
4647     }
4648 }
4649 
searchSlotValue(const STD_VECTOR<int> & restrict slotRemap,const int val) const4650 int Being::searchSlotValue(const STD_VECTOR<int> &restrict slotRemap,
4651                            const int val) const restrict2
4652 {
4653     const size_t sz = mSprites.size();
4654     for (size_t slot = 0; slot < sz; slot ++)
4655     {
4656         if (slotRemap[slot] == val)
4657             return CAST_S32(slot);
4658     }
4659     return CompoundSprite::getNumberOfLayers() - 1;
4660 }
4661 
searchSlotValueItr(STD_VECTOR<int>::iterator & restrict it,int & restrict idx,STD_VECTOR<int> & restrict slotRemap,const int val)4662 void Being::searchSlotValueItr(STD_VECTOR<int>::iterator &restrict it,
4663                                int &restrict idx,
4664                                STD_VECTOR<int> &restrict slotRemap,
4665                                const int val)
4666 {
4667 //    logger->log("searching %d", val);
4668     it = slotRemap.begin();
4669     const STD_VECTOR<int>::iterator it_end = slotRemap.end();
4670     idx = 0;
4671     while (it != it_end)
4672     {
4673 //        logger->log("testing %d", *it);
4674         if (*it == val)
4675         {
4676 //            logger->log("found at %d", idx);
4677             return;
4678         }
4679         ++ it;
4680         idx ++;
4681     }
4682 //    logger->log("not found");
4683     idx = -1;
4684 }
4685 
updateHit(const int amount)4686 void Being::updateHit(const int amount) restrict2
4687 {
4688     if (amount > 0)
4689     {
4690         if ((mMinHit == 0) || amount < mMinHit)
4691             mMinHit = amount;
4692         if (amount != mCriticalHit && ((mMaxHit == 0) || amount > mMaxHit))
4693             mMaxHit = amount;
4694     }
4695 }
4696 
getEquipment()4697 Equipment *Being::getEquipment() restrict2
4698 {
4699     Equipment *restrict const eq = new Equipment;
4700     Equipment::Backend *restrict const bk = new BeingEquipBackend(this);
4701     eq->setBackend(bk);
4702     return eq;
4703 }
4704 
undressItemById(const int id)4705 void Being::undressItemById(const int id) restrict2
4706 {
4707     const size_t sz = mSlots.size();
4708 
4709     for (size_t f = 0; f < sz; f ++)
4710     {
4711         if (id == mSlots[f].spriteId)
4712         {
4713             unSetSprite(CAST_U32(f));
4714             break;
4715         }
4716     }
4717 }
4718 
clearCache()4719 void Being::clearCache()
4720 {
4721     delete_all(beingInfoCache);
4722     beingInfoCache.clear();
4723 }
4724 
updateComment()4725 void Being::updateComment() restrict2
4726 {
4727     if (mGotComment || mName.empty())
4728         return;
4729 
4730     mGotComment = true;
4731     mComment = loadComment(mName, mType);
4732 }
4733 
loadComment(const std::string & restrict name,const ActorTypeT & restrict type)4734 std::string Being::loadComment(const std::string &restrict name,
4735                                const ActorTypeT &restrict type)
4736 {
4737     std::string str;
4738     switch (type)
4739     {
4740         case ActorType::Player:
4741             str = settings.usersDir;
4742             break;
4743         case ActorType::Npc:
4744             str = settings.npcsDir;
4745             break;
4746         case ActorType::Unknown:
4747         case ActorType::Monster:
4748         case ActorType::FloorItem:
4749         case ActorType::Portal:
4750         case ActorType::Avatar:
4751         case ActorType::Mercenary:
4752         case ActorType::Homunculus:
4753         case ActorType::Pet:
4754         case ActorType::SkillUnit:
4755         case ActorType::Elemental:
4756         default:
4757             return "";
4758     }
4759 
4760     str = pathJoin(str, stringToHexPath(name), "comment.txt");
4761     if (Files::existsLocal(str))
4762     {
4763         StringVect lines;
4764         Files::loadTextFileLocal(str, lines);
4765         if (lines.size() >= 2)
4766             return lines[1];
4767     }
4768     return std::string();
4769 }
4770 
saveComment(const std::string & restrict name,const std::string & restrict comment,const ActorTypeT & restrict type)4771 void Being::saveComment(const std::string &restrict name,
4772                         const std::string &restrict comment,
4773                         const ActorTypeT &restrict type)
4774 {
4775     std::string dir;
4776     switch (type)
4777     {
4778         case ActorType::Player:
4779             dir = settings.usersDir;
4780             break;
4781         case ActorType::Npc:
4782             dir = settings.npcsDir;
4783             break;
4784         case ActorType::Monster:
4785         case ActorType::FloorItem:
4786         case ActorType::Portal:
4787         case ActorType::Avatar:
4788         case ActorType::Unknown:
4789         case ActorType::Pet:
4790         case ActorType::Mercenary:
4791         case ActorType::Homunculus:
4792         case ActorType::SkillUnit:
4793         case ActorType::Elemental:
4794         default:
4795             return;
4796     }
4797     dir = pathJoin(dir, stringToHexPath(name));
4798     Files::saveTextFile(dir,
4799         "comment.txt",
4800         (name + "\n").append(comment));
4801 }
4802 
setState(const uint8_t state)4803 void Being::setState(const uint8_t state) restrict2
4804 {
4805     const bool shop = ((state & BeingFlag::SHOP) != 0);
4806     const bool away = ((state & BeingFlag::AWAY) != 0);
4807     const bool inactive = ((state & BeingFlag::INACTIVE) != 0);
4808     const bool needUpdate = (shop != mShop || away != mAway
4809         || inactive != mInactive);
4810 
4811     if (Net::getNetworkType() == ServerType::TMWATHENA)
4812         mShop = shop;
4813     mAway = away;
4814     mInactive = inactive;
4815     updateAwayEffect();
4816     showShopBadge(mShop);
4817     showInactiveBadge(mInactive);
4818     showAwayBadge(mAway);
4819 
4820     if (needUpdate)
4821     {
4822         if (shop || away || inactive)
4823             mAdvanced = true;
4824         updateName();
4825         addToCache();
4826     }
4827 }
4828 
setEmote(const uint8_t emotion,const int emote_time)4829 void Being::setEmote(const uint8_t emotion,
4830                      const int emote_time) restrict2
4831 {
4832     if ((emotion & BeingFlag::SPECIAL) == BeingFlag::SPECIAL)
4833     {
4834         setState(emotion);
4835         mAdvanced = true;
4836     }
4837     else
4838     {
4839         const int emotionIndex = emotion - 1;
4840         if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast())
4841         {
4842             delete2(mEmotionSprite)
4843             const EmoteInfo *const info = EmoteDB::get2(emotionIndex, true);
4844             if (info != nullptr)
4845             {
4846                 const EmoteSprite *restrict const sprite =
4847                     info->sprites.front();
4848                 if (sprite != nullptr)
4849                 {
4850                     mEmotionSprite = AnimatedSprite::clone(sprite->sprite);
4851                     if (mEmotionSprite != nullptr)
4852                         mEmotionTime = info->time;
4853                     else
4854                         mEmotionTime = emote_time;
4855                 }
4856                 const int effectId = info->effectId;
4857                 if (effectId >= 0)
4858                 {
4859                     effectManager->trigger(effectId, this, 0);
4860                 }
4861             }
4862         }
4863 
4864         if (mEmotionSprite != nullptr)
4865         {
4866             mEmotionSprite->play(mSpriteAction);
4867             mEmotionSprite->setSpriteDirection(mSpriteDirection);
4868         }
4869         else
4870         {
4871             mEmotionTime = 0;
4872         }
4873     }
4874 }
4875 
updatePercentHP()4876 void Being::updatePercentHP() restrict2
4877 {
4878     if (mMaxHP == 0)
4879         return;
4880     BLOCK_START("Being::updatePercentHP")
4881     if (mHP != 0)
4882     {
4883         const unsigned num = mHP * 100 / mMaxHP;
4884         if (num != mNumber)
4885         {
4886             mNumber = num;
4887             if (updateNumber(mNumber))
4888                 setAction(mAction, 0);
4889         }
4890     }
4891     BLOCK_END("Being::updatePercentHP")
4892 }
4893 
getSpriteID(const int slot) const4894 int Being::getSpriteID(const int slot) const restrict2
4895 {
4896     if (slot < 0 || CAST_SIZE(slot) >= mSlots.size())
4897         return -1;
4898 
4899     return mSlots[slot].spriteId;
4900 }
4901 
getSpriteSlot(const int slot) const4902 const BeingSlot &Being::getSpriteSlot(const int slot) const restrict2
4903 {
4904     if (slot < 0 || CAST_SIZE(slot) >= mSlots.size())
4905         return *emptyBeingSlot;
4906 
4907     return mSlots[slot];
4908 }
4909 
getSpriteColor(const int slot) const4910 ItemColor Being::getSpriteColor(const int slot) const restrict2
4911 {
4912     if (slot < 0 || CAST_SIZE(slot) >= mSlots.size())
4913         return ItemColor_one;
4914 
4915     return mSlots[slot].colorId;
4916 }
4917 
addAfkEffect()4918 void Being::addAfkEffect() restrict2
4919 {
4920     addSpecialEffect(mAwayEffect);
4921 }
4922 
removeAfkEffect()4923 void Being::removeAfkEffect() restrict2
4924 {
4925     removeSpecialEffect();
4926 }
4927 
addSpecialEffect(const int effect)4928 void Being::addSpecialEffect(const int effect) restrict2
4929 {
4930     if ((effectManager != nullptr) &&
4931         ParticleEngine::enabled &&
4932         (mSpecialParticle == nullptr) &&
4933         effect != -1)
4934     {
4935         mSpecialParticle = effectManager->triggerReturn(effect,
4936             this,
4937             0);
4938     }
4939 }
4940 
removeSpecialEffect()4941 void Being::removeSpecialEffect() restrict2
4942 {
4943     if ((effectManager != nullptr) && (mSpecialParticle != nullptr))
4944     {
4945         mChildParticleEffects.removeLocally(mSpecialParticle);
4946         mSpecialParticle = nullptr;
4947     }
4948     delete2(mAnimationEffect)
4949 }
4950 
updateAwayEffect()4951 void Being::updateAwayEffect() restrict2
4952 {
4953     if (mAway)
4954         addAfkEffect();
4955     else
4956         removeAfkEffect();
4957 }
4958 
addEffect(const std::string & restrict name)4959 void Being::addEffect(const std::string &restrict name) restrict2
4960 {
4961     delete mAnimationEffect;
4962     mAnimationEffect = AnimatedSprite::load(
4963         paths.getStringValue("sprites") + name,
4964         0);
4965 }
4966 
playSfx(const SoundInfo & sound,Being * const being,const bool main,const int x,const int y) const4967 void Being::playSfx(const SoundInfo &sound,
4968                     Being *const being,
4969                     const bool main,
4970                     const int x, const int y) const restrict2
4971 {
4972     BLOCK_START("Being::playSfx")
4973 
4974     if (being != nullptr)
4975     {
4976         // here need add timer and delay sound
4977         const int time = tick_time;
4978         if (main)
4979         {
4980             being->mNextSound.sound = nullptr;
4981             being->mNextSound.time = time + sound.delay;
4982             soundManager.playSfx(sound.sound, x, y);
4983         }
4984         else if (mNextSound.time <= time)
4985         {   // old event sound time is gone. we can play new sound
4986             being->mNextSound.sound = nullptr;
4987             being->mNextSound.time = time + sound.delay;
4988             soundManager.playSfx(sound.sound, x, y);
4989         }
4990         else
4991         {   // old event sound in progress. need save sound and wait
4992             being->mNextSound.sound = &sound;
4993             being->mNextSound.x = x;
4994             being->mNextSound.y = y;
4995         }
4996     }
4997     else
4998     {
4999         soundManager.playSfx(sound.sound, x, y);
5000     }
5001     BLOCK_END("Being::playSfx")
5002 }
5003 
setLook(const uint16_t look)5004 void Being::setLook(const uint16_t look) restrict2
5005 {
5006     if (mType == ActorType::Player)
5007         setSubtype(mSubType, look);
5008 }
5009 
setTileCoords(const int x,const int y)5010 void Being::setTileCoords(const int x, const int y) restrict2
5011 {
5012     mX = x;
5013     mY = y;
5014     if (mMap != nullptr)
5015     {
5016         mPixelOffsetY = 0;
5017         mFixedOffsetY = mPixelOffsetY;
5018         mOldHeight = mMap->getHeightOffset(mX, mY);
5019         mNeedPosUpdate = true;
5020     }
5021 }
5022 
setMap(Map * restrict const map)5023 void Being::setMap(Map *restrict const map) restrict2
5024 {
5025     mCastEndTime = 0;
5026     delete2(mCastingEffect)
5027     ActorSprite::setMap(map);
5028     if (mMap != nullptr)
5029     {
5030         mPixelOffsetY = mMap->getHeightOffset(mX, mY);
5031         mFixedOffsetY = mPixelOffsetY;
5032         mOldHeight = 0;
5033         mNeedPosUpdate = true;
5034     }
5035 }
5036 
removeAllItemsParticles()5037 void Being::removeAllItemsParticles() restrict2
5038 {
5039     FOR_EACH (SpriteParticleInfoIter, it, mSpriteParticles)
5040         delete (*it).second;
5041     mSpriteParticles.clear();
5042 }
5043 
addItemParticles(const int id,const SpriteDisplay & restrict display)5044 void Being::addItemParticles(const int id,
5045                              const SpriteDisplay &restrict display) restrict2
5046 {
5047     const SpriteParticleInfoIter it = mSpriteParticles.find(id);
5048     ParticleInfo *restrict pi = nullptr;
5049     if (it == mSpriteParticles.end())
5050     {
5051         pi = new ParticleInfo;
5052         mSpriteParticles[id] = pi;
5053     }
5054     else
5055     {
5056         pi = (*it).second;
5057     }
5058 
5059     if ((pi == nullptr) || !pi->particles.empty())
5060         return;
5061 
5062     // setup particle effects
5063     if (ParticleEngine::enabled &&
5064         (particleEngine != nullptr))
5065     {
5066         FOR_EACH (StringVectCIter, itr, display.particles)
5067         {
5068             Particle *const p = particleEngine->addEffect(*itr, 0, 0, 0);
5069             controlCustomParticle(p);
5070             pi->files.push_back(*itr);
5071             pi->particles.push_back(p);
5072         }
5073     }
5074     else
5075     {
5076         FOR_EACH (StringVectCIter, itr, display.particles)
5077             pi->files.push_back(*itr);
5078     }
5079 }
5080 
addItemParticlesCards(const int id,const SpriteDisplay & restrict display,const CardsList & cards)5081 void Being::addItemParticlesCards(const int id,
5082                                   const SpriteDisplay &restrict display,
5083                                   const CardsList &cards) restrict2
5084 {
5085     const SpriteParticleInfoIter it = mSpriteParticles.find(id);
5086     ParticleInfo *restrict pi = nullptr;
5087     if (it == mSpriteParticles.end())
5088     {
5089         pi = new ParticleInfo;
5090         mSpriteParticles[id] = pi;
5091     }
5092     else
5093     {
5094         pi = (*it).second;
5095     }
5096 
5097     if ((pi == nullptr) || !pi->particles.empty())
5098         return;
5099 
5100     // setup particle effects
5101     if (ParticleEngine::enabled &&
5102         (particleEngine != nullptr))
5103     {
5104         FOR_EACH (StringVectCIter, itr, display.particles)
5105         {
5106             Particle *const p = particleEngine->addEffect(*itr, 0, 0, 0);
5107             controlCustomParticle(p);
5108             pi->files.push_back(*itr);
5109             pi->particles.push_back(p);
5110         }
5111         for (int f = 0; f < maxCards; f ++)
5112         {
5113             const int cardId = cards.cards[f];
5114             if (!Item::isItem(cardId))
5115                 continue;
5116             const ItemInfo &info = ItemDB::get(cardId);
5117             const SpriteDisplay &restrict display2 = info.getDisplay();
5118             FOR_EACH (StringVectCIter, itr, display2.particles)
5119             {
5120                 Particle *const p = particleEngine->addEffect(*itr, 0, 0, 0);
5121                 controlCustomParticle(p);
5122                 pi->files.push_back(*itr);
5123                 pi->particles.push_back(p);
5124             }
5125         }
5126     }
5127     else
5128     {
5129         FOR_EACH (StringVectCIter, itr, display.particles)
5130         {
5131             pi->files.push_back(*itr);
5132         }
5133         for (int f = 0; f < maxCards; f ++)
5134         {
5135             const int cardId = cards.cards[f];
5136             if (!Item::isItem(cardId))
5137                 continue;
5138             const ItemInfo &info = ItemDB::get(cardId);
5139             const SpriteDisplay &restrict display2 = info.getDisplay();
5140             FOR_EACH (StringVectCIter, itr, display2.particles)
5141             {
5142                 pi->files.push_back(*itr);
5143             }
5144         }
5145     }
5146 }
5147 
removeItemParticles(const int id)5148 void Being::removeItemParticles(const int id) restrict2
5149 {
5150     const SpriteParticleInfoIter it = mSpriteParticles.find(id);
5151     if (it == mSpriteParticles.end())
5152         return;
5153     ParticleInfo *restrict const pi = (*it).second;
5154     if (pi != nullptr)
5155     {
5156         FOR_EACH (STD_VECTOR<Particle*>::const_iterator, itp, pi->particles)
5157             mChildParticleEffects.removeLocally(*itp);
5158         delete pi;
5159     }
5160     mSpriteParticles.erase(it);
5161 }
5162 
recreateItemParticles()5163 void Being::recreateItemParticles() restrict2
5164 {
5165     FOR_EACH (SpriteParticleInfoIter, it, mSpriteParticles)
5166     {
5167         ParticleInfo *restrict const pi = (*it).second;
5168         if ((pi != nullptr) && !pi->files.empty())
5169         {
5170             FOR_EACH (STD_VECTOR<Particle*>::const_iterator,
5171                       itp, pi->particles)
5172             {
5173                 mChildParticleEffects.removeLocally(*itp);
5174             }
5175 
5176             FOR_EACH (STD_VECTOR<std::string>::const_iterator, str, pi->files)
5177             {
5178                 Particle *const p = particleEngine->addEffect(
5179                     *str, 0, 0, 0);
5180                 controlCustomParticle(p);
5181                 pi->particles.push_back(p);
5182             }
5183         }
5184     }
5185 }
5186 
setTeamId(const uint16_t teamId)5187 void Being::setTeamId(const uint16_t teamId) restrict2
5188 {
5189     if (mTeamId != teamId)
5190     {
5191         mTeamId = teamId;
5192         showTeamBadge(mTeamId != 0);
5193         updateColors();
5194     }
5195 }
5196 
showTeamBadge(const bool show)5197 void Being::showTeamBadge(const bool show) restrict2
5198 {
5199     delete2(mBadges[BadgeIndex::Team])
5200     if (show &&
5201         mTeamId != 0U &&
5202         mShowBadges != BadgeDrawType::Hide)
5203     {
5204         const std::string name = paths.getStringValue("badges") +
5205             paths.getStringValue(strprintf("team%dbadge",
5206             mTeamId));
5207         if (!name.empty())
5208             mBadges[BadgeIndex::Team] = AnimatedSprite::load(name, 0);
5209     }
5210     updateBadgesCount();
5211     updateBadgesPosition();
5212 }
5213 
showBadges(const bool show)5214 void Being::showBadges(const bool show) restrict2
5215 {
5216     showTeamBadge(show);
5217     showGuildBadge(show);
5218     showGmBadge(show);
5219     showPartyBadge(show);
5220     showNameBadge(show);
5221     showShopBadge(show);
5222     showInactiveBadge(show);
5223     showAwayBadge(show);
5224 }
5225 
showPartyBadge(const bool show)5226 void Being::showPartyBadge(const bool show) restrict2
5227 {
5228     delete2(mBadges[BadgeIndex::Party])
5229     if (show &&
5230         !mPartyName.empty() &&
5231         mShowBadges != BadgeDrawType::Hide)
5232     {
5233         const std::string badge = BadgesDB::getPartyBadge(mPartyName);
5234         if (!badge.empty())
5235         {
5236             mBadges[BadgeIndex::Party] = AnimatedSprite::load(
5237                 paths.getStringValue("badges") + badge,
5238                 0);
5239         }
5240     }
5241     updateBadgesCount();
5242     updateBadgesPosition();
5243 }
5244 
5245 
setPartyName(const std::string & restrict name)5246 void Being::setPartyName(const std::string &restrict name) restrict2
5247 {
5248     if (mPartyName != name)
5249     {
5250         mPartyName = name;
5251         showPartyBadge(!mPartyName.empty());
5252     }
5253 }
5254 
showShopBadge(const bool show)5255 void Being::showShopBadge(const bool show) restrict2
5256 {
5257     delete2(mBadges[BadgeIndex::Shop])
5258     if (show &&
5259         mShop &&
5260         mShowBadges != BadgeDrawType::Hide)
5261     {
5262         const std::string badge = paths.getStringValue("shopbadge");
5263         if (!badge.empty())
5264         {
5265             mBadges[BadgeIndex::Shop] = AnimatedSprite::load(
5266                 paths.getStringValue("badges") + badge,
5267                 0);
5268         }
5269     }
5270     updateBadgesCount();
5271     updateBadgesPosition();
5272 }
5273 
showInactiveBadge(const bool show)5274 void Being::showInactiveBadge(const bool show) restrict2
5275 {
5276     delete2(mBadges[BadgeIndex::Inactive])
5277     if (show &&
5278         mInactive &&
5279         mShowBadges != BadgeDrawType::Hide)
5280     {
5281         const std::string badge = paths.getStringValue("inactivebadge");
5282         if (!badge.empty())
5283         {
5284             mBadges[BadgeIndex::Inactive] = AnimatedSprite::load(
5285                 paths.getStringValue("badges") + badge,
5286                 0);
5287         }
5288     }
5289     updateBadgesCount();
5290     updateBadgesPosition();
5291 }
5292 
showAwayBadge(const bool show)5293 void Being::showAwayBadge(const bool show) restrict2
5294 {
5295     delete2(mBadges[BadgeIndex::Away])
5296     if (show &&
5297         mAway &&
5298         mShowBadges != BadgeDrawType::Hide)
5299     {
5300         const std::string badge = paths.getStringValue("awaybadge");
5301         if (!badge.empty())
5302         {
5303             mBadges[BadgeIndex::Away] = AnimatedSprite::load(
5304                 paths.getStringValue("badges") + badge,
5305                 0);
5306         }
5307     }
5308     updateBadgesCount();
5309     updateBadgesPosition();
5310 }
5311 
updateBadgesCount()5312 void Being::updateBadgesCount() restrict2
5313 {
5314     mBadgesCount = 0;
5315     for_each_badges()
5316     {
5317         if (mBadges[f] != nullptr)
5318             mBadgesCount ++;
5319     }
5320 }
5321 
setChat(ChatObject * restrict const obj)5322 void Being::setChat(ChatObject *restrict const obj) restrict2
5323 {
5324     delete mChat;
5325     mChat = obj;
5326 }
5327 
setSellBoard(const std::string & restrict text)5328 void Being::setSellBoard(const std::string &restrict text) restrict2
5329 {
5330     mShop = !text.empty() || !mBuyBoard.empty();
5331     mSellBoard = text;
5332     updateName();
5333     showShopBadge(mShop);
5334 }
5335 
setBuyBoard(const std::string & restrict text)5336 void Being::setBuyBoard(const std::string &restrict text) restrict2
5337 {
5338     mShop = !text.empty() || !mSellBoard.empty();
5339     mBuyBoard = text;
5340     updateName();
5341     showShopBadge(mShop);
5342 }
5343 
enableShop(const bool b)5344 void Being::enableShop(const bool b) restrict2
5345 {
5346     mShop = b;
5347     updateName();
5348     showShopBadge(mShop);
5349 }
5350 
isBuyShopEnabled() const5351 bool Being::isBuyShopEnabled() const restrict2
5352 {
5353     return mShop && (Net::getNetworkType() == ServerType::TMWATHENA ||
5354         !mBuyBoard.empty());
5355 }
5356 
isSellShopEnabled() const5357 bool Being::isSellShopEnabled() const restrict2
5358 {
5359     return mShop && (Net::getNetworkType() == ServerType::TMWATHENA ||
5360         !mSellBoard.empty());
5361 }
5362 
serverRemove()5363 void Being::serverRemove() restrict2 noexcept2
5364 {
5365     // remove some flags what can survive player remove and next visible
5366     mTrickDead = false;
5367 }
5368 
addCast(const int dstX,const int dstY,const int skillId,const int skillLevel,const int range,const int waitTimeTicks)5369 void Being::addCast(const int dstX,
5370                     const int dstY,
5371                     const int skillId,
5372                     const int skillLevel,
5373                     const int range,
5374                     const int waitTimeTicks)
5375 {
5376     if (waitTimeTicks <= 0)
5377     {
5378         mCastEndTime = 0;
5379         return;
5380     }
5381     mCastEndTime = tick_time + waitTimeTicks;
5382     SkillData *const data = skillDialog->getSkillDataByLevel(
5383         skillId,
5384         skillLevel);
5385     delete2(mCastingEffect)
5386     if (data != nullptr)
5387     {
5388         const std::string castingAnimation = data->castingAnimation;
5389         mCastingEffect = new CastingEffect(skillId,
5390             skillLevel,
5391             castingAnimation,
5392             dstX,
5393             dstY,
5394             range);
5395         mCastingEffect->setMap(mMap);
5396     }
5397     else
5398     {
5399         reportAlways("Want to draw casting for unknown skill %d",
5400             skillId)
5401     }
5402 }
5403 
removeHorse()5404 void Being::removeHorse() restrict2
5405 {
5406     delete_all(mUpHorseSprites);
5407     mUpHorseSprites.clear();
5408     delete_all(mDownHorseSprites);
5409     mDownHorseSprites.clear();
5410 }
5411 
setRiding(const bool b)5412 void Being::setRiding(const bool b) restrict2
5413 {
5414     if (serverFeatures->haveExtendedRiding())
5415         return;
5416 
5417     if (b == (mHorseId != 0))
5418         return;
5419     if (b)
5420         setHorse(1);
5421     else
5422         setHorse(0);
5423 }
5424 
setHorse(const int horseId)5425 void Being::setHorse(const int horseId) restrict2
5426 {
5427     if (mHorseId == horseId)
5428         return;
5429     mHorseId = horseId;
5430     setAction(mAction, 0);
5431     removeHorse();
5432     if (mHorseId != 0)
5433     {
5434         mHorseInfo = HorseDB::get(horseId, false);
5435         if (mHorseInfo != nullptr)
5436         {
5437             FOR_EACH (SpriteRefs, it, mHorseInfo->downSprites)
5438             {
5439                 const SpriteReference *restrict const ref = *it;
5440                 AnimatedSprite *const sprite = AnimatedSprite::load(
5441                     ref->sprite,
5442                     ref->variant);
5443                 mDownHorseSprites.push_back(sprite);
5444                 sprite->play(mSpriteAction);
5445                 sprite->setSpriteDirection(mSpriteDirection);
5446             }
5447             FOR_EACH (SpriteRefs, it, mHorseInfo->upSprites)
5448             {
5449                 const SpriteReference *restrict const ref = *it;
5450                 AnimatedSprite *const sprite = AnimatedSprite::load(
5451                     ref->sprite,
5452                     ref->variant);
5453                 mUpHorseSprites.push_back(sprite);
5454                 sprite->play(mSpriteAction);
5455                 sprite->setSpriteDirection(mSpriteDirection);
5456             }
5457         }
5458     }
5459     else
5460     {
5461         mHorseInfo = nullptr;
5462     }
5463 }
5464 
setTrickDead(const bool b)5465 void Being::setTrickDead(const bool b) restrict2
5466 {
5467     if (b != mTrickDead)
5468     {
5469         mTrickDead = b;
5470         setAction(mAction, 0);
5471     }
5472 }
5473 
setSpiritBalls(const unsigned int balls)5474 void Being::setSpiritBalls(const unsigned int balls) restrict2
5475 {
5476     if (!ParticleEngine::enabled)
5477     {
5478         mSpiritBalls = balls;
5479         return;
5480     }
5481 
5482     if (balls > mSpiritBalls)
5483     {
5484         const int effectId = paths.getIntValue("spiritEffectId");
5485         if (effectId != -1)
5486             addSpiritBalls(balls - mSpiritBalls, effectId);
5487     }
5488     else if (balls < mSpiritBalls)
5489     {
5490         removeSpiritBalls(mSpiritBalls - balls);
5491     }
5492     mSpiritBalls = balls;
5493 }
5494 
addSpiritBalls(const unsigned int balls,const int effectId)5495 void Being::addSpiritBalls(const unsigned int balls,
5496                            const int effectId) restrict2
5497 {
5498     if (effectManager == nullptr)
5499         return;
5500     for (unsigned int f = 0; f < balls; f ++)
5501     {
5502         Particle *const particle = effectManager->triggerReturn(
5503             effectId,
5504             this,
5505             0);
5506         mSpiritParticles.push_back(particle);
5507     }
5508 }
5509 
removeSpiritBalls(const unsigned int balls)5510 void Being::removeSpiritBalls(const unsigned int balls) restrict2
5511 {
5512     if (particleEngine == nullptr)
5513         return;
5514     for (unsigned int f = 0; f < balls && !mSpiritParticles.empty(); f ++)
5515     {
5516         const Particle *restrict const particle = mSpiritParticles.back();
5517         mChildParticleEffects.removeLocally(particle);
5518         mSpiritParticles.pop_back();
5519     }
5520 }
5521 
stopCast(const bool b)5522 void Being::stopCast(const bool b)
5523 {
5524     if (b && mAction == BeingAction::CAST)
5525         setAction(BeingAction::STAND, 0);
5526 }
5527 
fixDirectionOffsets(int & offsetX,int & offsetY) const5528 void Being::fixDirectionOffsets(int &offsetX,
5529                                 int &offsetY) const
5530 {
5531     const uint8_t dir = mDirection;
5532     if ((dir & BeingDirection::DOWN) != 0)
5533     {
5534         // do nothing
5535     }
5536     else if ((dir & BeingDirection::UP) != 0)
5537     {
5538         offsetX = -offsetX;
5539         offsetY = -offsetY;
5540     }
5541     else if ((dir & BeingDirection::LEFT) != 0)
5542     {
5543         const int tmp = offsetY;
5544         offsetY = offsetX;
5545         offsetX = -tmp;
5546     }
5547     else if ((dir & BeingDirection::RIGHT) != 0)
5548     {
5549         const int tmp = offsetY;
5550         offsetY = -offsetX;
5551         offsetX = tmp;
5552     }
5553 }
5554 
setLanguageId(const int lang)5555 void Being::setLanguageId(const int lang) restrict2 noexcept2
5556 {
5557     if (lang != mLanguageId)
5558     {
5559         delete2(mBadges[BadgeIndex::Lang])
5560         const std::string &badge = LanguageDb::getIcon(lang);
5561         if (!badge.empty())
5562         {
5563             mBadges[BadgeIndex::Lang] = AnimatedSprite::load(pathJoin(
5564                 paths.getStringValue("languageIcons"),
5565                 badge),
5566                 0);
5567         }
5568 
5569         mLanguageId = lang;
5570     }
5571     updateBadgesCount();
5572     updateBadgesPosition();
5573 }
5574 
createBeing(const BeingId id,const ActorTypeT type,const BeingTypeId subType,Map * const map)5575 Being *Being::createBeing(const BeingId id,
5576                           const ActorTypeT type,
5577                           const BeingTypeId subType,
5578                           Map *const map)
5579 {
5580     Being *const being = new Being(id,
5581         type);
5582     being->postInit(subType,
5583         map);
5584     return being;
5585 }
5586 
setGroupId(const int id)5587 void Being::setGroupId(const int id)
5588 {
5589     if (mGroupId != id)
5590     {
5591         mGroupId = id;
5592         const bool gm = GroupDb::getHighlightName(mGroupId);
5593         if (mIsGM != gm)
5594         {
5595             mIsGM = gm;
5596             updateColors();
5597         }
5598         showGmBadge(id != 0);
5599     }
5600 }
5601