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