ManaPlus
being.cpp
Go to the documentation of this file.
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 
50 
52 
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 
63 
64 #include "gui/windows/chatwindow.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"
92 #include "resources/db/emotedb.h"
94 #include "resources/db/horsedb.h"
97 #include "resources/db/monsterdb.h"
98 #include "resources/db/npcdb.h"
99 #include "resources/db/petdb.h"
101 
102 #include "resources/image/image.h"
103 
104 #include "resources/item/item.h"
105 
106 #include "resources/map/map.h"
107 
110 
112 
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;
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;
143 bool Being::mUseDiagonal = true;
145 int Being::mAwayEffect = -1;
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 
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 
286  mBadges[f] = nullptr;
287 }
288 
289 void Being::postInit(const BeingTypeId subtype,
290  Map *const map)
291 {
292  setMap(map);
293  setSubtype(subtype, 0);
294 
296 
297  switch (mType)
298  {
299  case ActorType::Player:
301  case ActorType::Pet:
304  showName1 = static_cast<VisibleName::Type>(
305  config.getIntValue("visiblenames"));
306  break;
307  case ActorType::Portal:
309  showName1 = VisibleName::Hide;
310  break;
311  default:
312  case ActorType::Unknown:
313  case ActorType::Npc:
314  case ActorType::Monster:
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 
338 {
339  config.removeListener("visiblenames", this);
341 
342  delete [] mSpriteRemap;
343  mSpriteRemap = nullptr;
344  delete [] mSpriteHide;
345  mSpriteHide = nullptr;
346  delete [] mSpriteDraw;
347  mSpriteDraw = nullptr;
348 
350  delete2(mBadges[f])
351 
354  delete2(mText)
358  mBadgesCount = 0;
359  delete2(mChat)
360  removeHorse();
361 
363  mSpiritParticles.clear();
364 }
365 
367 {
369 }
370 
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(),
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(),
405  mInfo->getColor(fromInt(mLook, ItemColor)));
406  mYDiff = mInfo->getSortOffsetY();
407  }
408  break;
410  mInfo = MercenaryDB::get(mSubType);
411  if (mInfo != nullptr)
412  {
413  setName(mInfo->getName());
414  setupSpriteDisplay(mInfo->getDisplay(),
417  mInfo->getColor(fromInt(mLook, ItemColor)));
418  mYDiff = mInfo->getSortOffsetY();
419  }
420  break;
422  mInfo = HomunculusDB::get(mSubType);
423  if (mInfo != nullptr)
424  {
425  setName(mInfo->getName());
426  setupSpriteDisplay(mInfo->getDisplay(),
429  mInfo->getColor(fromInt(mLook, ItemColor)));
430  mYDiff = mInfo->getSortOffsetY();
431  }
432  break;
434  mInfo = SkillUnitDb::get(mSubType);
435  if (mInfo != nullptr)
436  {
437  setName(mInfo->getName());
438  setupSpriteDisplay(mInfo->getDisplay(),
441  mInfo->getColor(fromInt(mLook, ItemColor)));
442  mYDiff = mInfo->getSortOffsetY();
443  }
444  break;
446  mInfo = ElementalDb::get(mSubType);
447  if (mInfo != nullptr)
448  {
449  setName(mInfo->getName());
450  setupSpriteDisplay(mInfo->getDisplay(),
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(),
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(),
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:
511  default:
512  reportAlways("Wrong being type %d in setSubType",
513  CAST_S32(mType))
514  break;
515  }
516 }
517 
519 {
520  if (mInfo == nullptr)
522 
523  return mInfo->getTargetCursorSize();
524 }
525 
527 {
529 
530  updateCoords();
531 
532  if (mText != nullptr)
533  {
534  mText->adviseXY(CAST_S32(pos.x),
535  CAST_S32(pos.y) - getHeight() - mText->getHeight() - 9,
536  mMoveNames);
537  }
538 }
539 
540 void Being::setDestination(const int dstX,
541  const int dstY) restrict2
542 {
543  if (mMap == nullptr)
544  return;
545 
546  setPath(mMap->findPath(mX,
547  mY,
548  dstX,
549  dstY,
550  getBlockWalkMask(),
551  20));
552 }
553 
555 {
556  mPath.clear();
557 }
558 
560 {
561  mPath = path;
562  if (mPath.empty())
563  return;
564 
565  if (mAction != BeingAction::MOVE && mAction != BeingAction::DEAD)
566  {
567  nextTile();
568  mActionTime = tick_time;
569  }
570 }
571 
572 void Being::setSpeech(const std::string &restrict text) restrict2
573 {
574  if (userPalette == nullptr)
575  return;
576 
577  // Remove colors
578  mSpeech = removeColors(text);
579 
580  // Trim whitespace
581  trim(mSpeech);
582 
583  const unsigned int lineLim = mConfLineLim;
584  if (lineLim > 0 && mSpeech.length() > lineLim)
585  mSpeech = mSpeech.substr(0, lineLim);
586 
587  trim(mSpeech);
588  if (mSpeech.empty())
589  return;
590 
591  const size_t sz = mSpeech.size();
592  int time = 0;
593  if (sz < 200)
594  time = CAST_S32(SPEECH_TIME - 300 + (3 * sz));
595 
596  if (time < CAST_S32(SPEECH_MIN_TIME))
597  time = CAST_S32(SPEECH_MIN_TIME);
598 
599  // Check for links
600  size_t start = mSpeech.find('[');
601  size_t e = mSpeech.find(']', start);
602 
603  while (start != std::string::npos && e != std::string::npos)
604  {
605  // Catch multiple embeds and ignore them so it doesn't crash the client.
606  while ((mSpeech.find('[', start + 1) != std::string::npos) &&
607  (mSpeech.find('[', start + 1) < e))
608  {
609  start = mSpeech.find('[', start + 1);
610  }
611 
612  size_t position = mSpeech.find('|');
613  if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
614  {
615  mSpeech.erase(e, 1);
616  mSpeech.erase(start, (position - start) + 1);
617  }
618  position = mSpeech.find("@@");
619 
620  while (position != std::string::npos)
621  {
622  mSpeech.erase(position, 2);
623  position = mSpeech.find('@');
624  }
625 
626  start = mSpeech.find('[', start + 1);
627  e = mSpeech.find(']', start);
628  }
629 
630  if (!mSpeech.empty())
631  {
632  mSpeechTime = time <= CAST_S32(SPEECH_MAX_TIME)
633  ? time : CAST_S32(SPEECH_MAX_TIME);
634  }
635 
636  const int speech = mSpeechType;
637  if (speech == BeingSpeech::TEXT_OVERHEAD)
638  {
639  delete mText;
640  mText = nullptr;
641  mText = new Text(mSpeech,
642  mPixelX,
643  mPixelY - getHeight(),
646  Speech_true,
647  nullptr);
648  mText->adviseXY(mPixelX,
649  (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
650  mMoveNames);
651  }
652  else
653  {
654  if (mSpeechBubble == nullptr)
655  createSpeechBubble();
656  if (mSpeechBubble != nullptr)
657  {
658  const bool isShowName = (speech == BeingSpeech::NAME_IN_BUBBLE);
659  mSpeechBubble->setCaption(isShowName ? mName : "",
660  &theme->getColor(ThemeColorId::BUBBLE_NAME, 255),
661  &theme->getColor(ThemeColorId::BUBBLE_NAME_OUTLINE, 255));
662  mSpeechBubble->setText(mSpeech, isShowName);
663  }
664  }
665 }
666 
667 void Being::takeDamage(Being *restrict const attacker,
668  const int amount,
669  const AttackTypeT type,
670  const int attackId,
671  const int level) restrict2
672 {
673  if (userPalette == nullptr || attacker == nullptr)
674  return;
675 
676  BLOCK_START("Being::takeDamage1")
677 
678  Font *font = nullptr;
679  const Color *color;
680 
681  if (gui != nullptr)
682  font = gui->getInfoParticleFont();
683 
684  // Selecting the right color
685  if (type == AttackType::CRITICAL || type == AttackType::FLEE)
686  {
687  if (type == AttackType::CRITICAL)
688  attacker->setCriticalHit(amount);
689 
690  if (attacker == localPlayer)
691  {
692  color = &userPalette->getColor(
694  255U);
695  }
696  else
697  {
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
709  255U);
710  }
711  else
712  {
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(
727  255U);
728  }
729  else
730  {
731  color = &userPalette->getColor(
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.
742  255U);
743  attacker->setEnemy(true);
744  attacker->updateColors();
745  }
746  else
747  {
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),
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),
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
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,
853  attackId,
854  level);
855  }
856  else
857  {
858  hitEffectId = getHitEffect(attacker,
860  attackId,
861  level);
862  }
863  if (hitEffectId >= 0)
864  effectManager->trigger(hitEffectId, this, 0);
865  }
866  }
867  BLOCK_END("Being::takeDamage2")
868 }
869 
870 int Being::getHitEffect(const Being *restrict const attacker,
871  const AttackTypeT type,
872  const int attackId,
873  const int level)
874 {
875  if (effectManager == nullptr)
876  return 0;
877 
878  BLOCK_START("Being::getHitEffect")
879  // Init the particle effect path based on current
880  // weapon or default.
881  int hitEffectId = 0;
882  if (type == AttackType::SKILL || type == AttackType::SKILLMISS)
883  {
884  const SkillData *restrict const data =
885  skillDialog->getSkillDataByLevel(attackId, level);
886  if (data == nullptr)
887  return -1;
888  if (type == AttackType::SKILL)
889  {
890  hitEffectId = data->hitEffectId;
891  if (hitEffectId == -1)
892  hitEffectId = paths.getIntValue("skillHitEffectId");
893  }
894  else
895  {
896  hitEffectId = data->missEffectId;
897  if (hitEffectId == -1)
898  hitEffectId = paths.getIntValue("skillMissEffectId");
899  }
900  }
901  else
902  {
903  if (attacker != nullptr)
904  {
905  const ItemInfo *restrict const attackerWeapon
906  = attacker->getEquippedWeapon();
907  if (attackerWeapon != nullptr &&
908  attacker->getType() == ActorType::Player)
909  {
910  if (type == AttackType::MISS)
911  hitEffectId = attackerWeapon->getMissEffectId();
912  else if (type != AttackType::CRITICAL)
913  hitEffectId = attackerWeapon->getHitEffectId();
914  else
915  hitEffectId = attackerWeapon->getCriticalHitEffectId();
916  }
917  else if (attacker->getType() == ActorType::Monster)
918  {
919  const BeingInfo *restrict const info = attacker->getInfo();
920  if (info != nullptr)
921  {
922  const Attack *restrict const atk =
923  info->getAttack(attackId);
924  if (atk != nullptr)
925  {
926  if (type == AttackType::MISS)
927  hitEffectId = atk->mMissEffectId;
928  else if (type != AttackType::CRITICAL)
929  hitEffectId = atk->mHitEffectId;
930  else
931  hitEffectId = atk->mCriticalHitEffectId;
932  }
933  else
934  {
935  hitEffectId = getDefaultEffectId(type);
936  }
937  }
938  }
939  else
940  {
941  hitEffectId = getDefaultEffectId(type);
942  }
943  }
944  else
945  {
946  hitEffectId = getDefaultEffectId(type);
947  }
948  }
949  BLOCK_END("Being::getHitEffect")
950  return hitEffectId;
951 }
952 
954 {
955  if (type == AttackType::MISS)
956  return paths.getIntValue("missEffectId");
957  else if (type != AttackType::CRITICAL)
958  return paths.getIntValue("hitEffectId");
959  else
960  return paths.getIntValue("criticalHitEffectId");
961 }
962 
963 void Being::handleAttack(Being *restrict const victim,
964  const int damage,
965  const int attackId) restrict2
966 {
967  if ((victim == nullptr) || (mInfo == nullptr))
968  return;
969 
970  BLOCK_START("Being::handleAttack")
971 
972  if (this != localPlayer)
973  setAction(BeingAction::ATTACK, attackId);
974 
975  mLastAttackX = victim->mX;
976  mLastAttackY = victim->mY;
977 
978  if (mType == ActorType::Player && (mEquippedWeapon != nullptr))
979  fireMissile(victim, mEquippedWeapon->getMissileConst());
980  else if (mInfo->getAttack(attackId) != nullptr)
981  fireMissile(victim, mInfo->getAttack(attackId)->mMissile);
982 
983  reset();
984  mActionTime = tick_time;
985 
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 
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 
1035  skillId,
1036  skillLevel);
1037 
1038  if (data != nullptr)
1039  {
1040  effectManager->triggerDefault(data->castingSrcEffectId,
1041  this,
1042  paths.getIntValue("skillCastingSrcEffectId"));
1043  effectManager->triggerDefault(data->castingDstEffectId,
1044  victim,
1045  paths.getIntValue("skillCastingDstEffectId"));
1046  fireMissile(victim, data->castingMissile);
1047  }
1048 }
1049 
1050 void Being::handleSkill(Being *restrict const victim,
1051  const int damage,
1052  const int skillId,
1053  const int skillLevel) restrict2
1054 {
1055  if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1056  return;
1057 
1058  const SkillInfo *restrict const skill = skillDialog->getSkill(skillId);
1059  const SkillData *restrict const data = skill != nullptr
1060  ? skill->getData1(skillLevel) : nullptr;
1061  if (data != nullptr)
1062  {
1063  effectManager->triggerDefault(data->srcEffectId,
1064  this,
1065  paths.getIntValue("skillSrcEffectId"));
1066  effectManager->triggerDefault(data->dstEffectId,
1067  victim,
1068  paths.getIntValue("skillDstEffectId"));
1069  fireMissile(victim, data->missile);
1070  }
1071 
1072  if (this != localPlayer && (skill != nullptr))
1073  {
1074  const SkillType::SkillType type = skill->type;
1075  if ((type & SkillType::Attack) != 0 ||
1076  (type & SkillType::Ground) != 0)
1077  {
1078  setAction(BeingAction::ATTACK, 1);
1079  }
1080  else
1081  {
1082  setAction(BeingAction::STAND, 1);
1083  }
1084  }
1085 
1086  reset();
1087  mActionTime = tick_time;
1088 
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) ?
1113  victim,
1114  true,
1115  mX, mY);
1116  }
1117 }
1118 
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  {
1130  paths.getStringValue("badges") + badge,
1131  0);
1132  }
1133  }
1134 }
1135 
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 
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 
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  {
1190  paths.getStringValue("badges") + badge,
1191  0);
1192  }
1193  }
1194 }
1195 
1196 void Being::setGuildName(const std::string &restrict name) restrict2
1197 {
1198  if (mGuildName != name)
1199  {
1200  mGuildName = name;
1201  showGuildBadge(!mGuildName.empty());
1202  updateBadgesCount();
1203  }
1204 }
1205 
1206 void Being::showClanBadge(const bool show) restrict2
1207 {
1208  delete2(mBadges[BadgeIndex::Clan])
1209  if (show &&
1210  !mClanName.empty() &&
1211  mShowBadges != BadgeDrawType::Hide)
1212  {
1213  const std::string badge = BadgesDB::getClanBadge(mClanName);
1214  if (!badge.empty())
1215  {
1217  paths.getStringValue("badges") + badge,
1218  0);
1219  }
1220  }
1221 }
1222 
1223 void Being::setClanName(const std::string &restrict name) restrict2
1224 {
1225  if (mClanName != name)
1226  {
1227  mClanName = name;
1228  showClanBadge(!mClanName.empty());
1229  updateBadgesCount();
1230  }
1231 }
1232 
1233 void Being::setGuildPos(const std::string &restrict pos A_UNUSED) restrict2
1234 {
1235 }
1236 
1238 {
1239  if (guild == nullptr)
1240  return;
1241 
1242  mGuilds[guild->getId()] = guild;
1243 
1244  if (this == localPlayer && (socialWindow != nullptr))
1246 }
1247 
1248 void Being::removeGuild(const int id) restrict2
1249 {
1250  if (this == localPlayer && (socialWindow != nullptr))
1252 
1253  if (mGuilds[id] != nullptr)
1254  mGuilds[id]->removeMember(mName);
1255  mGuilds.erase(id);
1256 }
1257 
1258 const Guild *Being::getGuild(const std::string &restrict guildName) const
1259  restrict2
1260 {
1262  {
1263  const Guild *restrict const guild = itr->second;
1264  if ((guild != nullptr) && guild->getName() == guildName)
1265  return guild;
1266  }
1267 
1268  return nullptr;
1269 }
1270 
1271 const Guild *Being::getGuild(const int id) const restrict2
1272 {
1273  const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
1274  if (itr != mGuilds.end())
1275  return itr->second;
1276 
1277  return nullptr;
1278 }
1279 
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 
1290 {
1292  {
1293  Guild *const guild = itr->second;
1294 
1295  if (guild != nullptr)
1296  {
1297  if (this == localPlayer && (socialWindow != nullptr))
1299 
1300  guild->removeMember(mId);
1301  }
1302  }
1303 
1304  mGuilds.clear();
1305 }
1306 
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)
1330  }
1331 }
1332 
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 
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)
1375  }
1376 }
1377 
1378 void Being::fireMissile(Being *restrict const victim,
1379  const MissileInfo &restrict missile) const restrict2
1380 {
1381  BLOCK_START("Being::fireMissile")
1382 
1383  if (victim == nullptr ||
1384  missile.particle.empty() ||
1385  particleEngine == nullptr)
1386  {
1387  BLOCK_END("Being::fireMissile")
1388  return;
1389  }
1390 
1391  Particle *restrict const target = particleEngine->createChild();
1392 
1393  if (target == nullptr)
1394  {
1395  BLOCK_END("Being::fireMissile")
1396  return;
1397  }
1398 
1399  // +++ add z particle position?
1400  Particle *restrict const missileParticle = target->addEffect(
1401  missile.particle,
1402  mPixelX,
1403  mPixelY,
1404  0);
1405 
1406  target->moveBy(Vector(0.0F, 0.0F, missile.z));
1407  target->setLifetime(missile.lifeTime);
1408  victim->controlAutoParticle(target);
1409 
1410  if (missileParticle != nullptr)
1411  {
1412  missileParticle->setDestination(target, missile.speed, 0.0F);
1413  missileParticle->setDieDistance(missile.dieDistance);
1414  missileParticle->setLifetime(missile.lifeTime);
1415  }
1416  BLOCK_END("Being::fireMissile")
1417 }
1418 
1419 std::string Being::getSitAction() const restrict2
1420 {
1421  if (mHorseId != 0)
1422  return SpriteAction::SITRIDE;
1423  if (mMap != nullptr)
1424  {
1425  const unsigned char mask = mMap->getBlockMask(mX, mY);
1426  if ((mask & BlockMask::GROUNDTOP) != 0)
1427  return SpriteAction::SITTOP;
1428  else if ((mask & BlockMask::AIR) != 0)
1429  return SpriteAction::SITSKY;
1430  else if ((mask & BlockMask::WATER) != 0)
1431  return SpriteAction::SITWATER;
1432  }
1433  return SpriteAction::SIT;
1434 }
1435 
1436 
1437 std::string Being::getMoveAction() const restrict2
1438 {
1439  if (mHorseId != 0)
1440  return SpriteAction::RIDE;
1441  if (mMap != nullptr)
1442  {
1443  const unsigned char mask = mMap->getBlockMask(mX, mY);
1444  if ((mask & BlockMask::AIR) != 0)
1445  return SpriteAction::FLY;
1446  else if ((mask & BlockMask::WATER) != 0)
1447  return SpriteAction::SWIM;
1448  }
1449  return SpriteAction::MOVE;
1450 }
1451 
1452 std::string Being::getWeaponAttackAction(const ItemInfo *restrict const weapon)
1453  const restrict2
1454 {
1455  if (weapon == nullptr)
1456  return getAttackAction();
1457 
1458  if (mHorseId != 0)
1459  return weapon->getRideAttackAction();
1460  if (mMap != nullptr)
1461  {
1462  const unsigned char mask = mMap->getBlockMask(mX, mY);
1463  if ((mask & BlockMask::AIR) != 0)
1464  return weapon->getSkyAttackAction();
1465  else if ((mask & BlockMask::WATER) != 0)
1466  return weapon->getWaterAttackAction();
1467  }
1468  return weapon->getAttackAction();
1469 }
1470 
1471 std::string Being::getAttackAction(const Attack *restrict const attack1) const
1472  restrict2
1473 {
1474  if (attack1 == nullptr)
1475  return getAttackAction();
1476 
1477  if (mHorseId != 0)
1478  return attack1->mRideAction;
1479  if (mMap != nullptr)
1480  {
1481  const unsigned char mask = mMap->getBlockMask(mX, mY);
1482  if ((mask & BlockMask::AIR) != 0)
1483  return attack1->mSkyAction;
1484  else if ((mask & BlockMask::WATER) != 0)
1485  return attack1->mWaterAction;
1486  }
1487  return attack1->mAction;
1488 }
1489 
1490 std::string Being::getCastAction(const SkillInfo *restrict const skill) const
1491  restrict2
1492 {
1493  if (skill == nullptr)
1494  return getCastAction();
1495 
1496  if (mHorseId != 0)
1497  return skill->castingRideAction;
1498  if (mMap != nullptr)
1499  {
1500  const unsigned char mask = mMap->getBlockMask(mX, mY);
1501  if ((mask & BlockMask::AIR) != 0)
1502  return skill->castingSkyAction;
1503  else if ((mask & BlockMask::WATER) != 0)
1504  return skill->castingWaterAction;
1505  }
1506  return skill->castingAction;
1507 }
1508 
1509 #define getSpriteAction(func, action) \
1510  std::string Being::get##func##Action() const restrict2\
1511 { \
1512  if (mHorseId != 0) \
1513  return SpriteAction::action##RIDE; \
1514  if (mMap) \
1515  { \
1516  const unsigned char mask = mMap->getBlockMask(mX, mY); \
1517  if (mask & BlockMask::AIR) \
1518  return SpriteAction::action##SKY; \
1519  else if (mask & BlockMask::WATER) \
1520  return SpriteAction::action##WATER; \
1521  } \
1522  return SpriteAction::action; \
1523 }
1524 
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 
1555  const int attackId) restrict2
1556 {
1557  std::string currentAction = SpriteAction::INVALID;
1558 
1559  switch (action)
1560  {
1561  case BeingAction::MOVE:
1562  if (mInfo != nullptr)
1563  {
1564  playSfx(mInfo->getSound(
1565  ItemSoundEvent::MOVE), nullptr, true, mX, mY);
1566  }
1567  currentAction = getMoveAction();
1568  // Note: When adding a run action,
1569  // Differentiate walk and run with action name,
1570  // while using only the ACTION_MOVE.
1571  break;
1572  case BeingAction::SIT:
1573  currentAction = getSitAction();
1574  if (mInfo != nullptr)
1575  {
1576  ItemSoundEvent::Type event;
1577  if (currentAction == SpriteAction::SITTOP)
1578  event = ItemSoundEvent::SITTOP;
1579  else
1580  event = ItemSoundEvent::SIT;
1581  playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
1582  }
1583  break;
1584  case BeingAction::ATTACK:
1585  if (mEquippedWeapon != nullptr)
1586  {
1587  currentAction = getWeaponAttackAction(mEquippedWeapon);
1588  reset();
1589  }
1590  else
1591  {
1592  if (mInfo == nullptr || mInfo->getAttack(attackId) == nullptr)
1593  break;
1594 
1595  currentAction = getAttackAction(mInfo->getAttack(attackId));
1596  reset();
1597 
1598  // attack particle effect
1599  if (ParticleEngine::enabled && (effectManager != nullptr))
1600  {
1601  const int effectId = mInfo->getAttack(attackId)->mEffectId;
1602  if (effectId >= 0)
1603  {
1604  effectManager->triggerDirection(effectId,
1605  this,
1606  mSpriteDirection);
1607  }
1608  }
1609  }
1610  break;
1611  case BeingAction::CAST:
1612  if (skillDialog != nullptr)
1613  {
1614  const SkillInfo *restrict const info =
1615  skillDialog->getSkill(attackId);
1616  currentAction = getCastAction(info);
1617  }
1618  break;
1619  case BeingAction::HURT:
1620  if (mInfo != nullptr)
1621  {
1622  playSfx(mInfo->getSound(ItemSoundEvent::HURT),
1623  this, false, mX, mY);
1624  }
1625  break;
1626  case BeingAction::DEAD:
1627  currentAction = getDeadAction();
1628  if (mInfo != nullptr)
1629  {
1630  playSfx(mInfo->getSound(ItemSoundEvent::DIE),
1631  this,
1632  false,
1633  mX, mY);
1634  if (mType == ActorType::Monster ||
1635  mType == ActorType::Npc ||
1636  mType == ActorType::SkillUnit)
1637  {
1638  mYDiff = mInfo->getDeadSortOffsetY();
1639  }
1640  }
1641  break;
1642  case BeingAction::STAND:
1643  currentAction = getStandAction();
1644  break;
1645  case BeingAction::SPAWN:
1646  if (mInfo != nullptr)
1647  {
1648  playSfx(mInfo->getSound(ItemSoundEvent::SPAWN),
1649  nullptr, true, mX, mY);
1650  }
1651  currentAction = getSpawnAction();
1652  break;
1653  case BeingAction::PRESTAND:
1654  break;
1655  default:
1656  logger->log("Being::setAction unknown action: "
1657  + toString(CAST_U32(action)));
1658  break;
1659  }
1660 
1661  if (currentAction != SpriteAction::INVALID)
1662  {
1663  mSpriteAction = currentAction;
1664  play(currentAction);
1665  if (mEmotionSprite != nullptr)
1666  mEmotionSprite->play(currentAction);
1667  if (mAnimationEffect != nullptr)
1668  mAnimationEffect->play(currentAction);
1669  for_each_badges()
1670  {
1671  AnimatedSprite *const sprite = mBadges[f];
1672  if (sprite != nullptr)
1673  sprite->play(currentAction);
1674  }
1675  for_each_horses(mDownHorseSprites)
1676  (*it)->play(currentAction);
1677  for_each_horses(mUpHorseSprites)
1678  (*it)->play(currentAction);
1679  mAction = action;
1680  }
1681 
1682  if (currentAction != SpriteAction::MOVE
1683  && currentAction != SpriteAction::FLY
1684  && currentAction != SpriteAction::SWIM)
1685  {
1686  mActionTime = tick_time;
1687  }
1688 }
1689 
1690 void Being::setDirection(const uint8_t direction) restrict2
1691 {
1692  if (mDirection == direction)
1693  return;
1694 
1695  mDirection = direction;
1696 
1697  mDirectionDelayed = 0;
1698 
1699  // if the direction does not change much, keep the common component
1700  int mFaceDirection = mDirection & direction;
1701  if (mFaceDirection == 0)
1702  mFaceDirection = direction;
1703 
1705  if ((mFaceDirection & BeingDirection::UP) != 0)
1706  {
1707  if ((mFaceDirection & BeingDirection::LEFT) != 0)
1709  else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1711  else
1712  dir = SpriteDirection::UP;
1713  }
1714  else if ((mFaceDirection & BeingDirection::DOWN) != 0)
1715  {
1716  if ((mFaceDirection & BeingDirection::LEFT) != 0)
1718  else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
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 
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 
1754 {
1755  uint8_t dir = 0;
1756  if (mDest.x > mX)
1757  dir |= BeingDirection::RIGHT;
1758  else if (mDest.x < mX)
1759  dir |= BeingDirection::LEFT;
1760  if (mDest.y > mY)
1761  dir |= BeingDirection::DOWN;
1762  else if (mDest.y < mY)
1763  dir |= BeingDirection::UP;
1764  return dir;
1765 }
1766 
1767 uint8_t Being::calcDirection(const int dstX,
1768  const int dstY) const restrict2
1769 {
1770  uint8_t dir = 0;
1771  if (dstX > mX)
1772  dir |= BeingDirection::RIGHT;
1773  else if (dstX < mX)
1774  dir |= BeingDirection::LEFT;
1775  if (dstY > mY)
1776  dir |= BeingDirection::DOWN;
1777  else if (dstY < mY)
1778  dir |= BeingDirection::UP;
1779  return dir;
1780 }
1781 
1783 {
1784  if (mPath.empty())
1785  {
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  {
1802  return;
1803  }
1804 
1805  mActionTime += mSpeed / 10;
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  {
1820  mMap->getBlockMask(mX, mY) != mMap->getBlockMask(pos.x, pos.y))
1821  {
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;
1832 }
1833 
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 ||
1848  {
1849  botLogic();
1850  }
1851  }
1852 
1853  const int time = tick_time * MILLISECONDS_IN_A_TICK;
1854  if (mEmotionSprite != nullptr)
1855  mEmotionSprite->update(time);
1857  (*it)->update(time);
1859  (*it)->update(time);
1860 
1862  {
1863  mCastEndTime = 0;
1865  }
1866 
1868  {
1869  mAnimationEffect->update(time);
1872  }
1874  {
1875  mCastingEffect->update(time);
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  {
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)
1928  break;
1929  }
1930  }
1931 
1933  {
1934  const int xOffset = getOffset<BeingDirection::LEFT,
1936  const int yOffset = getOffset<BeingDirection::UP,
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 
1962  {
1963  mEmotionTime--;
1964  if (mEmotionTime == 0)
1966  }
1967 
1969 
1970  if (frameCount < 10)
1971  frameCount = 10;
1972 
1973  if (A_UNLIKELY(!isAlive() &&
1974  mSpeed != 0 &&
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 
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  {
2021  else
2023  mBotAi = true;
2024  return;
2025  }
2026  if (!mBotAi)
2027  return;
2028  if (mAction == BeingAction::MOVE)
2029  {
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();
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 
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 
2154  int dstY,
2155  const int divX,
2156  const int divY)
2157 {
2158  const int followDist = mInfo->getStartFollowDist();
2159  const int dist = mInfo->getFollowDist();
2160  if (divX > followDist || divY > followDist)
2161  {
2162  if (dist > 0)
2163  {
2164  if (divX > followDist)
2165  {
2166  if (dstX > mX + dist)
2167  dstX -= dist;
2168  else if (dstX + dist <= mX)
2169  dstX += dist;
2170  }
2171  else
2172  {
2173  dstX = mX;
2174  }
2175  if (divY > followDist)
2176  {
2177  if (dstY > mY + dist)
2178  dstY -= dist;
2179  else if (dstX + dist <= mX)
2180  dstY += dist;
2181  }
2182  else
2183  {
2184  dstY = mY;
2185  }
2186  }
2187  botFixOffset(dstX, dstY);
2188  moveBotTo(dstX, dstY);
2189  }
2190 }
2191 
2192 void Being::moveBotTo(int dstX,
2193  int dstY)
2194 {
2195  const int dstX0 = mOwner->mX;
2196  const int dstY0 = mOwner->mY;
2197  const unsigned char blockWalkMask = getBlockWalkMask();
2198  if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2199  {
2200  if (dstX != dstX0)
2201  {
2202  dstX = dstX0;
2203  if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2204  dstY = dstY0;
2205  }
2206  else if (dstY != dstY0)
2207  {
2208  dstY = dstY0;
2209  if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2210  dstX = dstX0;
2211  }
2212  }
2213  if (mX != dstX || mY != dstY)
2214  {
2216  homunculusHandler->move(dstX, dstY);
2217  else
2218  mercenaryHandler->move(dstX, dstY);
2219  return;
2220  }
2221  updateBotDirection(dstX, dstY);
2222 }
2223 
2224 void Being::updateBotDirection(const int dstX,
2225  const int dstY)
2226 {
2227  int directionType = 0;
2228  switch (mOwner->getCurrentAction())
2229  {
2230  case BeingAction::STAND:
2231  case BeingAction::MOVE:
2232  case BeingAction::HURT:
2233  case BeingAction::SPAWN:
2234  case BeingAction::CAST:
2235  case BeingAction::PRESTAND:
2236  default:
2237  directionType = mInfo->getDirectionType();
2238  break;
2239  case BeingAction::SIT:
2240  directionType = mInfo->getSitDirectionType();
2241  break;
2242  case BeingAction::DEAD:
2243  directionType = mInfo->getDeadDirectionType();
2244  break;
2245  case BeingAction::ATTACK:
2246  directionType = mInfo->getAttackDirectionType();
2247  break;
2248  }
2249 
2250  uint8_t newDir = 0;
2251  switch (directionType)
2252  {
2253  case 0:
2254  default:
2255  return;
2256 
2257  case 1:
2258  newDir = mOwner->mDirection;
2259  break;
2260 
2261  case 2:
2262  {
2263  const int dstX0 = mOwner->mX;
2264  const int dstY0 = mOwner->mY;
2265  if (dstX > dstX0)
2266  newDir |= BeingDirection::LEFT;
2267  else if (dstX < dstX0)
2268  newDir |= BeingDirection::RIGHT;
2269  if (dstY > dstY0)
2270  newDir |= BeingDirection::UP;
2271  else if (dstY < dstY0)
2272  newDir |= BeingDirection::DOWN;
2273  break;
2274  }
2275  case 3:
2276  {
2277  const int dstX0 = mOwner->mX;
2278  const int dstY0 = mOwner->mY;
2279  if (dstX > dstX0)
2280  newDir |= BeingDirection::RIGHT;
2281  else if (dstX < dstX0)
2282  newDir |= BeingDirection::LEFT;
2283  if (dstY > dstY0)
2284  newDir |= BeingDirection::DOWN;
2285  else if (dstY < dstY0)
2286  newDir |= BeingDirection::UP;
2287  break;
2288  }
2289  case 4:
2290  {
2291  const int dstX2 = mOwner->getLastAttackX();
2292  const int dstY2 = mOwner->getLastAttackY();
2293  if (dstX > dstX2)
2294  newDir |= BeingDirection::LEFT;
2295  else if (dstX < dstX2)
2296  newDir |= BeingDirection::RIGHT;
2297  if (dstY > dstY2)
2298  newDir |= BeingDirection::UP;
2299  else if (dstY < dstY2)
2300  newDir |= BeingDirection::DOWN;
2301  break;
2302  }
2303  }
2304  if ((newDir != 0U) && newDir != mDirection)
2305  {
2308  else
2309  mercenaryHandler->setDirection(newDir);
2310  }
2311 }
2312 
2314 {
2315  const int px = mPixelX - mapTileSize / 2;
2316  const int py = mPixelY - mapTileSize * 2 - mapTileSize;
2318  mBadgesCount != 0U)
2319  {
2320  if (mDispName != nullptr &&
2321  gui != nullptr)
2322  {
2324  {
2325  const Font *restrict const font = gui->getFont();
2327  mBadgesY = mDispName->getY() - font->getHeight();
2328  }
2329  else if (mShowBadges == BadgeDrawType::Bottom)
2330  {
2331  mBadgesX = px + 8 - mBadgesCount * 8;
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;
2345  mBadgesY = py - mDispName->getHeight();
2346  else
2347  mBadgesY = py;
2348  }
2349  }
2350  else
2351  {
2353  {
2355  mBadgesY = py;
2356  }
2357  else if (mShowBadges == BadgeDrawType::Bottom)
2358  {
2359  mBadgesX = px + 8 - mBadgesCount * 8;
2360  const int height = settings.playerNameOffset;
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 
2375 void Being::drawEmotion(Graphics *restrict const graphics,
2376  const int offsetX,
2377  const int offsetY) const restrict2
2378 {
2379  if (mErased)
2380  return;
2381 
2382  const int px = mPixelX - offsetX - mapTileSize / 2;
2383  const int py = mPixelY - offsetY - mapTileSize * 2 - mapTileSize;
2384  if (mAnimationEffect != nullptr)
2385  mAnimationEffect->draw(graphics, px, py);
2386  if (mShowBadges != BadgeDrawType::Hide &&
2387  mBadgesCount != 0U)
2388  {
2389  int x = mBadgesX - offsetX;
2390  const int y = mBadgesY - offsetY;
2391  for_each_badges()
2392  {
2393  const AnimatedSprite *restrict const sprite = mBadges[f];
2394  if (sprite != nullptr)
2395  {
2396  sprite->draw(graphics, x, y);
2397  x += 16;
2398  }
2399  }
2400  }
2401  if (mEmotionSprite != nullptr)
2402  mEmotionSprite->draw(graphics, px, py);
2403 }
2404 
2405 void Being::drawSpeech(const int offsetX,
2406  const int offsetY) restrict2
2407 {
2408  if (mErased)
2409  return;
2410  if (mSpeech.empty())
2411  return;
2412 
2413  const int px = mPixelX - offsetX;
2414  const int py = mPixelY - offsetY;
2415  const int speech = mSpeechType;
2416 
2417  // Draw speech above this being
2418  if (mSpeechTime == 0)
2419  {
2420  if (mSpeechBubble != nullptr &&
2421  mSpeechBubble->mVisible == Visible_true)
2422  {
2423  mSpeechBubble->setVisible(Visible_false);
2424  }
2425  mSpeech.clear();
2426  }
2427  else if (mSpeechTime > 0 && (speech == BeingSpeech::NAME_IN_BUBBLE ||
2428  speech == BeingSpeech::NO_NAME_IN_BUBBLE))
2429  {
2430  delete2(mText)
2431 
2432  if (mSpeechBubble != nullptr)
2433  {
2434  mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
2435  py - getHeight() - (mSpeechBubble->getHeight()));
2436  mSpeechBubble->setVisible(Visible_true);
2437  }
2438  }
2439  else if (mSpeechTime > 0 && speech == BeingSpeech::TEXT_OVERHEAD)
2440  {
2441  if (mSpeechBubble != nullptr)
2442  mSpeechBubble->setVisible(Visible_false);
2443 
2444  if ((mText == nullptr) && (userPalette != nullptr))
2445  {
2446  mText = new Text(mSpeech,
2447  mPixelX,
2448  mPixelY - getHeight(),
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>
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 
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 ||
2515  {
2516  offsetY += - settings.playerNameOffset - mDispName->getHeight();
2517  }
2518  mDispName->adviseXY(offsetX, offsetY, mMoveNames);
2519  }
2521 }
2522 
2523 void Being::optionChanged(const std::string &restrict value) restrict2
2524 {
2525  if (mType == ActorType::Player && value == "visiblenames")
2526  {
2527  setShowName(config.getIntValue("visiblenames") == VisibleName::Show);
2528  updateBadgesPosition();
2529  }
2530 }
2531 
2532 void Being::flashName(const int time) restrict2
2533 {
2534  if (mDispName != nullptr)
2535  mDispName->flash(time);
2536 }
2537 
2539 {
2540  const std::string &restrict str = getGenderSign();
2541  if (str.empty())
2542  return str;
2543  else
2544  return std::string(" ").append(str);
2545 }
2546 
2547 std::string Being::getGenderSign() const restrict2
2548 {
2549  std::string str;
2550  if (mShowGender)
2551  {
2552  if (getGender() == Gender::FEMALE)
2553  str = "\u2640";
2554  else if (getGender() == Gender::MALE)
2555  str = "\u2642";
2556  else if (mType == ActorType::Player)
2557  str = "\u2640";
2558  }
2559  if (mShowPlayersStatus &&
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 
2579 {
2580  if (mName.empty())
2581  return;
2582 
2584 
2586  return;
2587 
2588  std::string displayName(mName);
2589 
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,
2623  mNameColor,
2624  font);
2625  }
2626  else
2627  {
2628  mDispName = new FlashText(displayName,
2629  mPixelX,
2630  mPixelY,
2632  mNameColor,
2633  font);
2634  }
2635 
2636  updateCoords();
2637 }
2638 
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 
2664 {
2665  if (userPalette != nullptr)
2666  {
2667  if (mType == ActorType::Monster)
2668  {
2671  255U);
2672  }
2673  else if (mType == ActorType::Npc)
2674  {
2677  255U);
2678  }
2679  else if (mType == ActorType::Pet)
2680  {
2683  255U);
2684  }
2685  else if (mType == ActorType::Homunculus)
2686  {
2689  255U);
2690  }
2691  else if (mType == ActorType::SkillUnit)
2692  {
2695  255U);
2696  }
2697  else if (this == localPlayer)
2698  {
2700  mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2701  }
2702  else
2703  {
2704  mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255);
2705 
2707  mErased = false;
2708  else
2709  mErased = true;
2710 
2711  if (mIsGM)
2712  {
2714  255U);
2716  255U);
2717  }
2718  else if (mEnemy)
2719  {
2721  255U);
2722  }
2723  else if ((mParty != nullptr) && (localPlayer != nullptr)
2724  && mParty == localPlayer->getParty())
2725  {
2727  255U);
2728  }
2729  else if ((localPlayer != nullptr) && (getGuild() != nullptr)
2730  && getGuild() == localPlayer->getGuild())
2731  {
2733  255U);
2734  }
2736  {
2738  255U);
2739  }
2740  else if (playerRelations.getRelation(mName) ==
2744  {
2746  255U);
2747  }
2749  == Relation::IGNORED ||
2751  {
2753  255U);
2754  }
2756  {
2758  255U);
2759  }
2760  else
2761  {
2763  }
2764  }
2765 
2766  if (mDispName != nullptr)
2768  }
2769 }
2770 
2771 void Being::updateSprite(const unsigned int slot,
2772  const int id,
2773  const std::string &restrict color) restrict2
2774 {
2775  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2776  return;
2777 
2778  if (slot >= CAST_U32(mSlots.size()))
2779  mSlots.resize(slot + 1, BeingSlot());
2780 
2781  if ((slot != 0U) && mSlots[slot].spriteId == id)
2782  return;
2783  setSpriteColor(slot,
2784  id,
2785  color);
2786 }
2787 
2788 // set sprite id, reset colors, reset cards
2789 void Being::setSpriteId(const unsigned int slot,
2790  const int id) restrict2
2791 {
2792  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2793  return;
2794 
2795  if (slot >= CAST_U32(mSprites.size()))
2796  ensureSize(slot + 1);
2797 
2798  if (slot >= CAST_U32(mSlots.size()))
2799  mSlots.resize(slot + 1, BeingSlot());
2800 
2801  // id = 0 means unequip
2802  if (id == 0)
2803  {
2804  removeSprite(slot);
2805  mSpriteDraw[slot] = 0;
2806 
2807  const int id1 = mSlots[slot].spriteId;
2808  if (id1 != 0)
2809  removeItemParticles(id1);
2810  }
2811  else
2812  {
2813  const ItemInfo &info = ItemDB::get(id);
2814  const std::string &restrict filename = info.getSprite(
2815  mGender, mSubType);
2816  int lastTime = 0;
2817  int startTime = 0;
2818  AnimatedSprite *restrict equipmentSprite = nullptr;
2819 
2820  if (!filename.empty())
2821  {
2822  equipmentSprite = AnimatedSprite::delayedLoad(
2823  pathJoin(paths.getStringValue("sprites"), filename),
2824  0);
2825  }
2826 
2827  if (equipmentSprite != nullptr)
2828  {
2829  equipmentSprite->setSpriteDirection(getSpriteDirection());
2830  startTime = getStartTime();
2831  lastTime = getLastTime();
2832  }
2833 
2834  CompoundSprite::setSprite(slot, equipmentSprite);
2835  mSpriteDraw[slot] = id;
2836 
2837  addItemParticles(id, info.getDisplay());
2838 
2839  setAction(mAction, 0);
2840  if (equipmentSprite != nullptr)
2841  {
2842  if (lastTime > 0)
2843  {
2844  equipmentSprite->setLastTime(startTime);
2845  equipmentSprite->update(lastTime);
2846  }
2847  }
2848  }
2849 
2850  BeingSlot &beingSlot = mSlots[slot];
2851  beingSlot.spriteId = id;
2852  beingSlot.color.clear();
2853  beingSlot.colorId = ItemColor_one;
2854  beingSlot.cardsId = CardsList(nullptr);
2855  recalcSpritesOrder();
2856  if (beingEquipmentWindow != nullptr)
2858 }
2859 
2860 // reset sprite id, reset colors, reset cards
2861 void Being::unSetSprite(const unsigned int slot) restrict2
2862 {
2863  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2864  return;
2865 
2866  if (slot >= CAST_U32(mSprites.size()))
2867  ensureSize(slot + 1);
2868 
2869  if (slot >= CAST_U32(mSlots.size()))
2870  mSlots.resize(slot + 1, BeingSlot());
2871 
2872  removeSprite(slot);
2873  mSpriteDraw[slot] = 0;
2874 
2875  BeingSlot &beingSlot = mSlots[slot];
2876  const int id1 = beingSlot.spriteId;
2877  if (id1 != 0)
2878  removeItemParticles(id1);
2879 
2880  beingSlot.spriteId = 0;
2881  beingSlot.color.clear();
2882  beingSlot.colorId = ItemColor_one;
2883  beingSlot.cardsId = CardsList(nullptr);
2884  recalcSpritesOrder();
2885  if (beingEquipmentWindow != nullptr)
2887 }
2888 
2889 // set sprite id, use color string, reset cards
2890 void Being::setSpriteColor(const unsigned int slot,
2891  const int id,
2892  const std::string &color) restrict2
2893 {
2894  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2895  return;
2896 
2897  if (slot >= CAST_U32(mSprites.size()))
2898  ensureSize(slot + 1);
2899 
2900  if (slot >= CAST_U32(mSlots.size()))
2901  mSlots.resize(slot + 1, BeingSlot());
2902 
2903  // disabled for now, because it may broke replace/reorder sprites logic
2904 // if (slot && mSlots[slot].spriteId == id)
2905 // return;
2906 
2907  // id = 0 means unequip
2908  if (id == 0)
2909  {
2910  removeSprite(slot);
2911  mSpriteDraw[slot] = 0;
2912 
2913  const int id1 = mSlots[slot].spriteId;
2914  if (id1 != 0)
2915  removeItemParticles(id1);
2916  }
2917  else
2918  {
2919  const ItemInfo &info = ItemDB::get(id);
2920  const std::string &restrict filename = info.getSprite(
2921  mGender, mSubType);
2922  int lastTime = 0;
2923  int startTime = 0;
2924  AnimatedSprite *restrict equipmentSprite = nullptr;
2925 
2926  if (!filename.empty())
2927  {
2928  equipmentSprite = AnimatedSprite::delayedLoad(
2929  pathJoin(paths.getStringValue("sprites"),
2930  combineDye(filename, color)),
2931  0);
2932  }
2933 
2934  if (equipmentSprite != nullptr)
2935  {
2936  equipmentSprite->setSpriteDirection(getSpriteDirection());
2937  startTime = getStartTime();
2938  lastTime = getLastTime();
2939  }
2940 
2941  CompoundSprite::setSprite(slot, equipmentSprite);
2942  mSpriteDraw[slot] = id;
2943  addItemParticles(id, info.getDisplay());
2944 
2945  setAction(mAction, 0);
2946  if (equipmentSprite != nullptr)
2947  {
2948  if (lastTime > 0)
2949  {
2950  equipmentSprite->setLastTime(startTime);
2951  equipmentSprite->update(lastTime);
2952  }
2953  }
2954  }
2955 
2956  BeingSlot &beingSlot = mSlots[slot];
2957  beingSlot.spriteId = id;
2958  beingSlot.color = color;
2959  beingSlot.colorId = ItemColor_one;
2960  beingSlot.cardsId = CardsList(nullptr);
2961  recalcSpritesOrder();
2962  if (beingEquipmentWindow != nullptr)
2964 }
2965 
2966 // set sprite id, use color id, reset cards
2967 void Being::setSpriteColorId(const unsigned int slot,
2968  const int id,
2969  ItemColor colorId) restrict2
2970 {
2971  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
2972  return;
2973 
2974  if (slot >= CAST_U32(mSprites.size()))
2975  ensureSize(slot + 1);
2976 
2977  if (slot >= CAST_U32(mSlots.size()))
2978  mSlots.resize(slot + 1, BeingSlot());
2979 
2980  // disabled for now, because it may broke replace/reorder sprites logic
2981 // if (slot && mSlots[slot].spriteId == id)
2982 // return;
2983 
2984  std::string color;
2985 
2986  // id = 0 means unequip
2987  if (id == 0)
2988  {
2989  removeSprite(slot);
2990  mSpriteDraw[slot] = 0;
2991 
2992  const int id1 = mSlots[slot].spriteId;
2993  if (id1 != 0)
2994  removeItemParticles(id1);
2995  }
2996  else
2997  {
2998  const ItemInfo &info = ItemDB::get(id);
2999  const std::string &restrict filename = info.getSprite(
3000  mGender, mSubType);
3001  int lastTime = 0;
3002  int startTime = 0;
3003  AnimatedSprite *restrict equipmentSprite = nullptr;
3004 
3005  if (!filename.empty())
3006  {
3007  color = info.getDyeColorsString(colorId);
3008  equipmentSprite = AnimatedSprite::delayedLoad(
3009  pathJoin(paths.getStringValue("sprites"),
3010  combineDye(filename, color)),
3011  0);
3012  }
3013 
3014  if (equipmentSprite != nullptr)
3015  {
3016  equipmentSprite->setSpriteDirection(getSpriteDirection());
3017  startTime = getStartTime();
3018  lastTime = getLastTime();
3019  }
3020 
3021  CompoundSprite::setSprite(slot, equipmentSprite);
3022  mSpriteDraw[slot] = id;
3023 
3024  addItemParticles(id, info.getDisplay());
3025 
3026  setAction(mAction, 0);
3027  if (equipmentSprite != nullptr)
3028  {
3029  if (lastTime > 0)
3030  {
3031  equipmentSprite->setLastTime(startTime);
3032  equipmentSprite->update(lastTime);
3033  }
3034  }
3035  }
3036 
3037  BeingSlot &beingSlot = mSlots[slot];
3038  beingSlot.spriteId = id;
3039  beingSlot.color = STD_MOVE(color);
3040  beingSlot.colorId = colorId;
3041  beingSlot.cardsId = CardsList(nullptr);
3042  recalcSpritesOrder();
3043  if (beingEquipmentWindow != nullptr)
3045 }
3046 
3047 // set sprite id, colors from cards, cards
3048 void Being::setSpriteCards(const unsigned int slot,
3049  const int id,
3050  const CardsList &cards) restrict2
3051 {
3052  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3053  return;
3054 
3055  if (slot >= CAST_U32(mSprites.size()))
3056  ensureSize(slot + 1);
3057 
3058  if (slot >= CAST_U32(mSlots.size()))
3059  mSlots.resize(slot + 1, BeingSlot());
3060 
3061  // disabled for now, because it may broke replace/reorder sprites logic
3062 // if (slot && mSlots[slot].spriteId == id)
3063 // return;
3064 
3065  ItemColor colorId = ItemColor_one;
3066  std::string color;
3067 
3068  // id = 0 means unequip
3069  if (id == 0)
3070  {
3071  removeSprite(slot);
3072  mSpriteDraw[slot] = 0;
3073 
3074  const int id1 = mSlots[slot].spriteId;
3075  if (id1 != 0)
3076  removeItemParticles(id1);
3077  }
3078  else
3079  {
3080  const ItemInfo &info = ItemDB::get(id);
3081  const std::string &restrict filename = info.getSprite(
3082  mGender, mSubType);
3083  int lastTime = 0;
3084  int startTime = 0;
3085  AnimatedSprite *restrict equipmentSprite = nullptr;
3086 
3087  if (!cards.isEmpty())
3088  colorId = ItemColorManager::getColorFromCards(cards);
3089 
3090  if (!filename.empty())
3091  {
3092  color = info.getDyeColorsString(colorId);
3093 
3094  equipmentSprite = AnimatedSprite::delayedLoad(
3095  pathJoin(paths.getStringValue("sprites"),
3096  combineDye(filename, color)),
3097  0);
3098  }
3099 
3100  if (equipmentSprite != nullptr)
3101  {
3102  equipmentSprite->setSpriteDirection(getSpriteDirection());
3103  startTime = getStartTime();
3104  lastTime = getLastTime();
3105  }
3106 
3107  CompoundSprite::setSprite(slot, equipmentSprite);
3108  mSpriteDraw[slot] = id;
3109 
3110  addItemParticlesCards(id,
3111  info.getDisplay(),
3112  cards);
3113 
3114  setAction(mAction, 0);
3115  if (equipmentSprite != nullptr)
3116  {
3117  if (lastTime > 0)
3118  {
3119  equipmentSprite->setLastTime(startTime);
3120  equipmentSprite->update(lastTime);
3121  }
3122  }
3123  }
3124 
3125  BeingSlot &beingSlot = mSlots[slot];
3126  beingSlot.spriteId = id;
3127  beingSlot.color = STD_MOVE(color);
3128  beingSlot.colorId = colorId;
3129  beingSlot.cardsId = CardsList(cards);
3130  recalcSpritesOrder();
3131  if (beingEquipmentWindow != nullptr)
3133 }
3134 
3135 void Being::setWeaponId(const int id) restrict2
3136 {
3137  if (id == 0)
3138  mEquippedWeapon = nullptr;
3139  else
3140  mEquippedWeapon = &ItemDB::get(id);
3141 }
3142 
3143 void Being::setTempSprite(const unsigned int slot,
3144  const int id) restrict2
3145 {
3146  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3147  return;
3148 
3149  if (slot >= CAST_U32(mSprites.size()))
3150  ensureSize(slot + 1);
3151 
3152  if (slot >= CAST_U32(mSlots.size()))
3153  mSlots.resize(slot + 1, BeingSlot());
3154 
3155  BeingSlot &beingSlot = mSlots[slot];
3156 
3157  // id = 0 means unequip
3158  if (id == 0)
3159  {
3160  removeSprite(slot);
3161  mSpriteDraw[slot] = 0;
3162 
3163  const int id1 = beingSlot.spriteId;
3164  if (id1 != 0)
3165  removeItemParticles(id1);
3166  }
3167  else
3168  {
3169  const ItemInfo &info = ItemDB::get(id);
3170  const std::string &restrict filename = info.getSprite(
3171  mGender, mSubType);
3172  int lastTime = 0;
3173  int startTime = 0;
3174 
3175  AnimatedSprite *restrict equipmentSprite = nullptr;
3176 
3177  if (!filename.empty())
3178  {
3179  ItemColor colorId = ItemColor_one;
3180  const CardsList &cards = beingSlot.cardsId;
3181  if (!cards.isEmpty())
3182  colorId = ItemColorManager::getColorFromCards(cards);
3183  std::string color = beingSlot.color;
3184  if (color.empty())
3185  color = info.getDyeColorsString(colorId);
3186 
3187  equipmentSprite = AnimatedSprite::delayedLoad(
3188  pathJoin(paths.getStringValue("sprites"),
3189  combineDye(filename, color)),
3190  0);
3191  }
3192 
3193  if (equipmentSprite != nullptr)
3194  {
3195  equipmentSprite->setSpriteDirection(getSpriteDirection());
3196  startTime = getStartTime();
3197  lastTime = getLastTime();
3198  }
3199 
3200  CompoundSprite::setSprite(slot, equipmentSprite);
3201  mSpriteDraw[slot] = id;
3202 
3203  // +++ here probably need use existing cards
3204  addItemParticles(id, info.getDisplay());
3205 
3206  setAction(mAction, 0);
3207  if (equipmentSprite != nullptr)
3208  {
3209  if (lastTime > 0)
3210  {
3211  equipmentSprite->setLastTime(startTime);
3212  equipmentSprite->update(lastTime);
3213  }
3214  }
3215  }
3216 }
3217 
3218 void Being::setHairTempSprite(const unsigned int slot,
3219  const int id) restrict2
3220 {
3221  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3222  return;
3223 
3224  if (slot >= CAST_U32(mSprites.size()))
3225  ensureSize(slot + 1);
3226 
3227  if (slot >= CAST_U32(mSlots.size()))
3228  mSlots.resize(slot + 1, BeingSlot());
3229 
3230  const CardsList &cards = mSlots[slot].cardsId;
3231 
3232  // id = 0 means unequip
3233  if (id == 0)
3234  {
3235  removeSprite(slot);
3236  mSpriteDraw[slot] = 0;
3237 
3238  const int id1 = mSlots[slot].spriteId;
3239  if (id1 != 0)
3240  removeItemParticles(id1);
3241  }
3242  else
3243  {
3244  const ItemInfo &info = ItemDB::get(id);
3245  const std::string &restrict filename = info.getSprite(
3246  mGender, mSubType);
3247  int lastTime = 0;
3248  int startTime = 0;
3249  AnimatedSprite *restrict equipmentSprite = nullptr;
3250 
3251  if (!filename.empty())
3252  {
3253  ItemColor colorId = ItemColor_one;
3254  if (!cards.isEmpty())
3255  colorId = ItemColorManager::getColorFromCards(cards);
3256 
3257  std::string color = info.getDyeColorsString(mHairColor);
3258  if (color.empty())
3259  color = info.getDyeColorsString(colorId);
3260 
3261  equipmentSprite = AnimatedSprite::delayedLoad(
3262  pathJoin(paths.getStringValue("sprites"),
3263  combineDye(filename, color)),
3264  0);
3265  }
3266 
3267  if (equipmentSprite != nullptr)
3268  {
3269  equipmentSprite->setSpriteDirection(getSpriteDirection());
3270  startTime = getStartTime();
3271  lastTime = getLastTime();
3272  }
3273 
3274  CompoundSprite::setSprite(slot, equipmentSprite);
3275  mSpriteDraw[slot] = id;
3276 
3277  addItemParticles(id, info.getDisplay());
3278 
3279  setAction(mAction, 0);
3280  if (equipmentSprite != nullptr)
3281  {
3282  if (lastTime > 0)
3283  {
3284  equipmentSprite->setLastTime(startTime);
3285  equipmentSprite->update(lastTime);
3286  }
3287  }
3288  }
3289 }
3290 
3291 void Being::setHairColorSpriteID(const unsigned int slot,
3292  const int id) restrict2
3293 {
3294  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3295  return;
3296 
3297  if (slot >= CAST_U32(mSprites.size()))
3298  ensureSize(slot + 1);
3299 
3300  if (slot >= CAST_U32(mSlots.size()))
3301  mSlots.resize(slot + 1, BeingSlot());
3302 
3303  BeingSlot &beingSlot = mSlots[slot];
3304  setSpriteColor(slot,
3305  id,
3306  beingSlot.color);
3307 }
3308 
3309 void Being::setSpriteColor(const unsigned int slot,
3310  const std::string &restrict color) restrict2
3311 {
3312  if (charServerHandler == nullptr || slot >= charServerHandler->maxSprite())
3313  return;
3314 
3315  if (slot >= CAST_U32(mSprites.size()))
3316  ensureSize(slot + 1);
3317 
3318  if (slot >= CAST_U32(mSlots.size()))
3319  mSlots.resize(slot + 1, BeingSlot());
3320 
3321  // disabled for now, because it may broke replace/reorder sprites logic
3322 // if (slot && mSlots[slot].spriteId == id)
3323 // return;
3324 
3325  BeingSlot &beingSlot = mSlots[slot];
3326  const int id = beingSlot.spriteId;
3327 
3328  // id = 0 means unequip
3329  if (id != 0)
3330  {
3331  const ItemInfo &info = ItemDB::get(id);
3332  const std::string &restrict filename = info.getSprite(
3333  mGender, mSubType);
3334  int lastTime = 0;
3335  int startTime = 0;
3336  AnimatedSprite *restrict equipmentSprite = nullptr;
3337 
3338  if (!filename.empty())
3339  {
3340  equipmentSprite = AnimatedSprite::delayedLoad(
3341  pathJoin(paths.getStringValue("sprites"),
3342  combineDye(filename, color)),
3343  0);
3344  }
3345 
3346  if (equipmentSprite != nullptr)
3347  {
3348  equipmentSprite->setSpriteDirection(getSpriteDirection());
3349  startTime = getStartTime();
3350  lastTime = getLastTime();
3351  }
3352 
3353  CompoundSprite::setSprite(slot, equipmentSprite);
3354 
3355  setAction(mAction, 0);
3356  if (equipmentSprite != nullptr)
3357  {
3358  if (lastTime > 0)
3359  {
3360  equipmentSprite->setLastTime(startTime);
3361  equipmentSprite->update(lastTime);
3362  }
3363  }
3364  }
3365 
3366  beingSlot.color = color;
3367  beingSlot.colorId = ItemColor_one;
3368  if (beingEquipmentWindow != nullptr)
3370 }
3371 
3372 void Being::setHairStyle(const unsigned int slot,
3373  const int id) restrict2
3374 {
3375  if (id != 0)
3376  {
3377  setSpriteColor(slot,
3378  id,
3379  ItemDB::get(id).getDyeColorsString(mHairColor));
3380  }
3381  else
3382  {
3383  setSpriteColor(slot,
3384  0,
3385  std::string());
3386  }
3387 }
3388 
3389 void Being::setHairColor(const unsigned int slot,
3390  const ItemColor color) restrict2
3391 {
3392  mHairColor = color;
3393  BeingSlot &beingSlot = mSlots[slot];
3394  const int id = beingSlot.spriteId;
3395  if (id != 0)
3396  {
3397  setSpriteColor(slot,
3398  id,
3399  ItemDB::get(id).getDyeColorsString(color));
3400  }
3401 }
3402 
3403 void Being::setSpriteSlot(const unsigned int slot,
3404  const BeingSlot &beingSlot)
3405 {
3406  mSlots[slot] = beingSlot;
3407 }
3408 
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 
3426 {
3427  if (mShowName)
3428  showName();
3429 }
3430 
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");
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 
3460  }
3461  BLOCK_END("Being::reReadConfig")
3462 }
3463 
3465 {
3466  const BeingCacheEntry *restrict const entry =
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) &&
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  {
3497  mShop = false;
3498  mAway = false;
3499  mInactive = false;
3500  }
3501 
3505  updateAwayEffect();
3506  if (mType == ActorType::Player || (mTeamId != 0U))
3507  updateColors();
3508  return true;
3509  }
3510  return false;
3511 }
3512 
3514 {
3515  if (localPlayer == this)
3516  return;
3517 
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;
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 
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 
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)
3639  }
3640  }
3641 
3642  updateName();
3643  }
3644 }
3645 
3646 void Being::showGmBadge(const bool show) restrict2
3647 {
3648  delete2(mBadges[BadgeIndex::Gm])
3649  if (show &&
3650  mIsGM &&
3651  mShowBadges != BadgeDrawType::Hide &&
3652  GroupDb::getShowBadge(mGroupId))
3653  {
3654  const std::string &gmBadge = GroupDb::getBadge(mGroupId);
3655  if (!gmBadge.empty())
3656  {
3658  paths.getStringValue("badges") + gmBadge,
3659  0);
3660  }
3661  }
3662  updateBadgesCount();
3663  updateBadgesPosition();
3664 }
3665 
3666 void Being::setGM(const bool gm) restrict2
3667 {
3668  if (mIsGM != gm)
3669  {
3670  mIsGM = gm;
3671  updateColors();
3672  }
3673 }
3674 
3676 {
3677  if (npcHandler == nullptr)
3678  return;
3679 
3681  {
3682  // using workaround...
3683  if ((playerHandler != nullptr) &&
3685  {
3687  }
3688  return;
3689  }
3690 
3691  npcHandler->talk(this);
3692 }
3693 
3694 void Being::drawPlayer(Graphics *restrict const graphics,
3695  const int offsetX,
3696  const int offsetY) const restrict2
3697 {
3698  if (!mErased)
3699  {
3700  // getActorX() + offsetX;
3701  const int px = mPixelX - mapTileSize / 2 + offsetX;
3702  // getActorY() + offsetY;
3703  const int py = mPixelY - mapTileSize + offsetY;
3704  if (mHorseInfo != nullptr)
3705  {
3706  HorseOffset &offset = mHorseInfo->offsets[mSpriteDirection];
3707  for_each_horses(mDownHorseSprites)
3708  {
3709  (*it)->draw(graphics,
3710  px + offset.downOffsetX,
3711  py + offset.downOffsetY);
3712  }
3713 
3714  drawBeingCursor(graphics, px, py);
3715  drawPlayerSpriteAt(graphics,
3716  px + offset.riderOffsetX,
3717  py + offset.riderOffsetY);
3718 
3719  for_each_horses(mUpHorseSprites)
3720  {
3721  (*it)->draw(graphics,
3722  px + offset.upOffsetX,
3723  py + offset.upOffsetY);
3724  }
3725  }
3726  else
3727  {
3728  drawBeingCursor(graphics, px, py);
3729  drawPlayerSpriteAt(graphics, px, py);
3730  }
3731  }
3732 }
3733 
3734 void Being::drawBeingCursor(Graphics *const graphics,
3735  const int offsetX,
3736  const int offsetY) const
3737 {
3738  if (mUsedTargetCursor != nullptr)
3739  {
3741  if (mInfo == nullptr)
3742  {
3743  mUsedTargetCursor->draw(graphics,
3744  offsetX - mCursorPaddingX,
3745  offsetY - mCursorPaddingY);
3746  }
3747  else
3748  {
3749  mUsedTargetCursor->draw(graphics,
3750  offsetX + mInfo->getTargetOffsetX() - mCursorPaddingX,
3751  offsetY + mInfo->getTargetOffsetY() - mCursorPaddingY);
3752  }
3753  }
3754 }
3755 
3756 void Being::drawOther(Graphics *restrict const graphics,
3757  const int offsetX,
3758  const int offsetY) const restrict2
3759 {
3760  // getActorX() + offsetX;
3761  const int px = mPixelX - mapTileSize / 2 + offsetX;
3762  // getActorY() + offsetY;
3763  const int py = mPixelY - mapTileSize + offsetY;
3764  drawBeingCursor(graphics, px, py);
3765  drawOtherSpriteAt(graphics, px, py);
3766 }
3767 
3768 void Being::drawNpc(Graphics *restrict const graphics,
3769  const int offsetX,
3770  const int offsetY) const restrict2
3771 {
3772  // getActorX() + offsetX;
3773  const int px = mPixelX - mapTileSize / 2 + offsetX;
3774  // getActorY() + offsetY;
3775  const int py = mPixelY - mapTileSize + offsetY;
3776  drawBeingCursor(graphics, px, py);
3777  drawNpcSpriteAt(graphics, px, py);
3778 }
3779 
3780 void Being::drawMonster(Graphics *restrict const graphics,
3781  const int offsetX,
3782  const int offsetY) const restrict2
3783 {
3784  // getActorX() + offsetX;
3785  const int px = mPixelX - mapTileSize / 2 + offsetX;
3786  // getActorY() + offsetY;
3787  const int py = mPixelY - mapTileSize + offsetY;
3788  drawBeingCursor(graphics, px, py);
3789  drawMonsterSpriteAt(graphics, px, py);
3790 }
3791 
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 
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 
3817  const int offsetX,
3818  const int offsetY) const restrict2
3819 {
3820  // getActorX() + offsetX;
3821  const int px = mPixelX - mapTileSize / 2 + offsetX;
3822  // getActorY() + offsetY;
3823  const int py = mPixelY - mapTileSize + offsetY;
3824  drawBeingCursor(graphics, px, py);
3825  drawElementalSpriteAt(graphics, px, py);
3826 }
3827 
3828 void Being::drawPortal(Graphics *restrict const graphics,
3829  const int offsetX,
3830  const int offsetY) const restrict2
3831 {
3832  // getActorX() + offsetX;
3833  const int px = mPixelX - mapTileSize / 2 + offsetX;
3834  // getActorY() + offsetY;
3835  const int py = mPixelY - mapTileSize + offsetY;
3836  drawPortalSpriteAt(graphics, px, py);
3837 }
3838 
3839 void Being::draw(Graphics *restrict const graphics,
3840  const int offsetX,
3841  const int offsetY) const restrict2
3842 {
3843  switch (mType)
3844  {
3845  case ActorType::Player:
3846  drawPlayer(graphics,
3847  offsetX,
3848  offsetY);
3849  break;
3850  case ActorType::Portal:
3851  drawPortal(graphics,
3852  offsetX,
3853  offsetY);
3854  break;
3855  case ActorType::Homunculus:
3856  drawHomunculus(graphics,
3857  offsetX,
3858  offsetY);
3859  break;
3860  case ActorType::Mercenary:
3861  drawMercenary(graphics,
3862  offsetX,
3863  offsetY);
3864  break;
3865  case ActorType::Elemental:
3866  drawElemental(graphics,
3867  offsetX,
3868  offsetY);
3869  break;
3870  case ActorType::Monster:
3871  drawMonster(graphics,
3872  offsetX,
3873  offsetY);
3874  break;
3875  case ActorType::Npc:
3876  drawNpc(graphics,
3877  offsetX,
3878  offsetY);
3879  break;
3880  case ActorType::Pet:
3881  case ActorType::SkillUnit:
3882  case ActorType::Unknown:
3883  case ActorType::FloorItem:
3884  case ActorType::Avatar:
3885  default:
3886  drawOther(graphics,
3887  offsetX,
3888  offsetY);
3889  break;
3890  }
3891 }
3892 
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 
3914  const int posX,
3915  const int posY) const restrict2
3916 {
3917  const size_t sz = mSprites.size();
3918  for (size_t f = 0; f < sz; f ++)
3919  {
3920  const int rSprite = mSpriteHide[mSpriteRemap[f]];
3921  if (rSprite == 1)
3922  continue;
3923 
3924  const Sprite *restrict const sprite = mSprites[mSpriteRemap[f]];
3925  if (sprite != nullptr)
3926  sprite->draw(graphics, posX, posY);
3927  }
3928 }
3929 
3930 void Being::drawBasic(Graphics *restrict const graphics,
3931  const int x,
3932  const int y) const restrict2
3933 {
3934  drawCompound(graphics, x, y);
3935 }
3936 
3937 void Being::drawCompound(Graphics *const graphics,
3938  const int posX,
3939  const int posY) const
3940 {
3941  FUNC_BLOCK("CompoundSprite::draw", 1)
3942  if (mNeedsRedraw)
3943  updateImages();
3944 
3945  if (mSprites.empty()) // Nothing to draw
3946  return;
3947 
3948  if (mAlpha == 1.0F && (mImage != nullptr))
3949  {
3950  graphics->drawImage(mImage,
3951  posX + mOffsetX,
3952  posY + mOffsetY);
3953  }
3954  else if ((mAlpha != 0.0F) && (mAlphaImage != nullptr))
3955  {
3956  mAlphaImage->setAlpha(mAlpha);
3957  graphics->drawImage(mAlphaImage,
3958  posX + mOffsetX,
3959  posY + mOffsetY);
3960  }
3961  else
3962  {
3963  Being::drawPlayerSprites(graphics, posX, posY);
3964  }
3965 }
3966 
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,
3981  0,
3984  x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
3985  y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
3986  2 * 50,
3987  4);
3988  }
3989 }
3990 
3992  const int x,
3993  const int y) const restrict2
3994 {
3995  CompoundSprite::drawSimple(graphics, x, y);
3996 }
3997 
3999  const int x,
4000  const int y) const restrict2
4001 {
4002  drawCompound(graphics, x, y);
4003 }
4004 
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(
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,
4052  x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4053  y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4054  2 * 50,
4055  4);
4056  }
4057 }
4058 
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(
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  {
4092  if ((info != nullptr) &&
4093  mId == info->id)
4094  {
4095  // show hp bar here
4097  if (maxHP == 0)
4098  maxHP = mInfo->getMaxHP();
4099 
4100  drawHpBar(graphics,
4101  maxHP,
4103  mDamageTaken,
4106  x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4107  y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4108  2 * 50,
4109  4);
4110  }
4111  }
4112 }
4113 
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(
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
4152  if (maxHP == 0)
4153  maxHP = mInfo->getMaxHP();
4154 
4155  drawHpBar(graphics,
4156  maxHP,
4158  mDamageTaken,
4161  x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4162  y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4163  2 * 50,
4164  4);
4165  }
4166  }
4167 }
4168 
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(
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
4205  if (maxHP == 0)
4206  maxHP = mInfo->getMaxHP();
4207 
4208  drawHpBar(graphics,
4209  maxHP,
4211  mDamageTaken,
4214  x - 50 + mapTileSize / 2 + mInfo->getHpBarOffsetX(),
4215  y + mapTileSize - 6 + mInfo->getHpBarOffsetY(),
4216  2 * 50,
4217  4);
4218  }
4219  }
4220 }
4221 
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,
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 
4253 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 
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 
4343 void Being::setMaxHP(const int hp) restrict2
4344 {
4345  mMaxHP = hp;
4346  if (mMaxHP < mHP)
4347  mMaxHP = mHP;
4348 }
4349 
4351 {
4352  mMoveTime = 0;
4353  mAttackTime = 0;
4354  mTalkTime = 0;
4355  mOtherTime = 0;
4356  mTestTime = cur_time;
4357 }
4358 
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 
4650 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 
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 
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 
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 
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 
4720 {
4722  beingInfoCache.clear();
4723 }
4724 
4726 {
4727  if (mGotComment || mName.empty())
4728  return;
4729 
4730  mGotComment = true;
4732 }
4733 
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 
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 
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 
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 
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 
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 
4894 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 
4902 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 
4911 {
4912  if (slot < 0 || CAST_SIZE(slot) >= mSlots.size())
4913  return ItemColor_one;
4914 
4915  return mSlots[slot].colorId;
4916 }
4917 
4919 {
4921 }
4922 
4924 {
4926 }
4927 
4928 void Being::addSpecialEffect(const int effect) restrict2
4929 {
4930  if ((effectManager != nullptr) &&
4932  (mSpecialParticle == nullptr) &&
4933  effect != -1)
4934  {
4935  mSpecialParticle = effectManager->triggerReturn(effect,
4936  this,
4937  0);
4938  }
4939 }
4940 
4942 {
4943  if ((effectManager != nullptr) && (mSpecialParticle != nullptr))
4944  {
4946  mSpecialParticle = nullptr;
4947  }
4949 }
4950 
4952 {
4953  if (mAway)
4954  addAfkEffect();
4955  else
4956  removeAfkEffect();
4957 }
4958 
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 
4967 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 
5004 void Being::setLook(const uint16_t look) restrict2
5005 {
5006  if (mType == ActorType::Player)
5007  setSubtype(mSubType, look);
5008 }
5009 
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 
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 
5038 {
5040  delete (*it).second;
5041  mSpriteParticles.clear();
5042 }
5043 
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
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 
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
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 
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 
5164 {
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  {
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);
5181  pi->particles.push_back(p);
5182  }
5183  }
5184  }
5185 }
5186 
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 
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 
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 
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  {
5237  paths.getStringValue("badges") + badge,
5238  0);
5239  }
5240  }
5241  updateBadgesCount();
5242  updateBadgesPosition();
5243 }
5244 
5245 
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 
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  {
5266  paths.getStringValue("badges") + badge,
5267  0);
5268  }
5269  }
5270  updateBadgesCount();
5271  updateBadgesPosition();
5272 }
5273 
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  {
5285  paths.getStringValue("badges") + badge,
5286  0);
5287  }
5288  }
5289  updateBadgesCount();
5290  updateBadgesPosition();
5291 }
5292 
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  {
5304  paths.getStringValue("badges") + badge,
5305  0);
5306  }
5307  }
5308  updateBadgesCount();
5309  updateBadgesPosition();
5310 }
5311 
5313 {
5314  mBadgesCount = 0;
5315  for_each_badges()
5316  {
5317  if (mBadges[f] != nullptr)
5318  mBadgesCount ++;
5319  }
5320 }
5321 
5323 {
5324  delete mChat;
5325  mChat = obj;
5326 }
5327 
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 
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 
5344 void Being::enableShop(const bool b) restrict2
5345 {
5346  mShop = b;
5347  updateName();
5348  showShopBadge(mShop);
5349 }
5350 
5352 {
5354  !mBuyBoard.empty());
5355 }
5356 
5358 {
5360  !mSellBoard.empty());
5361 }
5362 
5364 {
5365  // remove some flags what can survive player remove and next visible
5366  mTrickDead = false;
5367 }
5368 
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;
5383  skillId,
5384  skillLevel);
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);
5396  }
5397  else
5398  {
5399  reportAlways("Want to draw casting for unknown skill %d",
5400  skillId)
5401  }
5402 }
5403 
5405 {
5407  mUpHorseSprites.clear();
5409  mDownHorseSprites.clear();
5410 }
5411 
5412 void Being::setRiding(const bool b) restrict2
5413 {
5415  return;
5416 
5417  if (b == (mHorseId != 0))
5418  return;
5419  if (b)
5420  setHorse(1);
5421  else
5422  setHorse(0);
5423 }
5424 
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 
5465 void Being::setTrickDead(const bool b) restrict2
5466 {
5467  if (b != mTrickDead)
5468  {
5469  mTrickDead = b;
5470  setAction(mAction, 0);
5471  }
5472 }
5473 
5474 void Being::setSpiritBalls(const unsigned int balls) restrict2
5475 {
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 
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 
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 
5522 void Being::stopCast(const bool b)
5523 {
5524  if (b && mAction == BeingAction::CAST)
5526 }
5527 
5528 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 
5556 {
5557  if (lang != mLanguageId)
5558  {
5559  delete2(mBadges[BadgeIndex::Lang])
5560  const std::string &badge = LanguageDb::getIcon(lang);
5561  if (!badge.empty())
5562  {
5564  paths.getStringValue("languageIcons"),
5565  badge),
5566  0);
5567  }
5568 
5569  mLanguageId = lang;
5570  }
5571  updateBadgesCount();
5572  updateBadgesPosition();
5573 }
5574 
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 
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 }
ActorManager * actorManager
ActorType ::T ActorTypeT
Definition: actortype.h:43
AttackType ::T AttackTypeT
Definition: attacktype.h:47
const unsigned int CACHE_SIZE
Definition: being.cpp:125
#define getSpriteAction(func, action)
Definition: being.cpp:1509
#define for_each_badges()
Definition: being.cpp:156
#define for_each_horses(name)
Definition: being.cpp:159
std::map< int, Guild * >::const_iterator GuildsMapCIter
Definition: being.cpp:149
std::list< BeingCacheEntry * > beingInfoCache
Definition: being.cpp:148
std::map< int, int >::const_iterator IntMapCIter
Definition: being.cpp:150
static const unsigned int SPEECH_TIME
Definition: being.cpp:152
static const unsigned int SPEECH_MAX_TIME
Definition: being.cpp:154
static const unsigned int SPEECH_MIN_TIME
Definition: being.cpp:153
volatile time_t cur_time
Definition: timer.cpp:58
BeingAction ::T BeingActionT
Definition: beingaction.h:41
int BeingId
Definition: beingid.h:30
const BeingId BeingId_zero
Definition: beingid.h:30
BeingSlot * emptyBeingSlot
Definition: beingslot.cpp:26
int BeingTypeId
Definition: beingtypeid.h:30
#define fromBool(val, name)
Definition: booldefines.h:49
#define maxCards
Definition: cards.h:25
#define CAST_S32
Definition: cast.h:30
#define CAST_U32
Definition: cast.h:31
#define CAST_SIZE
Definition: cast.h:34
Net::CharServerHandler * charServerHandler
Definition: net.cpp:85
ChatWindow * chatWindow
Definition: chatwindow.cpp:94
#define reportTrue(val)
Definition: checkutils.h:252
#define reportAlways(...)
Definition: checkutils.h:253
void destroy(ActorSprite *const actor)
virtual void logic()
AnimatedSprite * mUsedTargetCursor
Definition: actorsprite.h:250
bool mTrickDead
Definition: actorsprite.h:263
int mCursorPaddingY
Definition: actorsprite.h:257
ParticleList mChildParticleEffects
Definition: actorsprite.h:245
void setMap(Map *const map)
BeingId mId
Definition: actorsprite.h:247
BeingId getId() const
Definition: actorsprite.h:64
void controlCustomParticle(Particle *const particle)
int mCursorPaddingX
Definition: actorsprite.h:256
virtual void setPixelPositionF(const Vector &pos)
Definition: actor.cpp:83
int mPixelX
Definition: actor.h:133
virtual void setMap(Map *const map)
Definition: actor.cpp:48
Map * mMap
Definition: actor.h:139
int mPixelY
Definition: actor.h:134
bool setSpriteDirection(const SpriteDirection::Type direction)
static AnimatedSprite * delayedLoad(const std::string &filename, const int variant)
bool play(const std::string &spriteAction)
void draw(Graphics *const graphics, const int posX, const int posY) const
static AnimatedSprite * load(const std::string &filename, const int variant)
bool isTerminated() const
static AnimatedSprite * clone(const AnimatedSprite *const anim)
bool update(const int time)
void setFlags(const int flags)
void setGuildName(const std::string &name)
void setPartyName(const std::string &name)
void setIp(const std::string &ip)
void setLevel(const int n)
void setTime(const time_t n)
void setAdvanced(const bool a)
void setTeamId(const uint16_t teamId)
void setName(const std::string &name)
void setPvpRank(const int r)
TargetCursorSizeT getTargetCursorSize() const
Definition: beinginfo.h:90
int getTargetOffsetY() const
Definition: beinginfo.h:141
int getDeadOffsetX() const
Definition: beinginfo.h:267
int getSitOffsetX() const
Definition: beinginfo.h:243
int getSitDirectionType() const
Definition: beinginfo.h:303
int getSitOffsetY() const
Definition: beinginfo.h:249
int getAttackOffsetX() const
Definition: beinginfo.h:279
int getFollowDist() const
Definition: beinginfo.h:225
int getMoveOffsetY() const
Definition: beinginfo.h:261
int getAttackDirectionType() const
Definition: beinginfo.h:315
int getTargetOffsetX() const
Definition: beinginfo.h:135
int getDirectionType() const
Definition: beinginfo.h:297
int getThinkTime() const
Definition: beinginfo.h:291
int getWarpDist() const
Definition: beinginfo.h:237
int getNameOffsetY() const
Definition: beinginfo.h:153
int getDeadOffsetY() const
Definition: beinginfo.h:273
int getAttackOffsetY() const
Definition: beinginfo.h:285
int getMoveOffsetX() const
Definition: beinginfo.h:255
int getStartFollowDist() const
Definition: beinginfo.h:219
int getNameOffsetX() const
Definition: beinginfo.h:147
int getDeadDirectionType() const
Definition: beinginfo.h:309
Definition: being.h:96
void showName()
Definition: being.cpp:2578
bool mNeedPosUpdate
Definition: being.h:1384
bool mAway
Definition: being.h:1382
void setTempSprite(const unsigned int slot, const int id)
Definition: being.cpp:3143
Being(const BeingId id, const ActorTypeT type)
Definition: being.cpp:162
void setHairStyle(const unsigned int slot, const int id)
Definition: being.cpp:3372
void updateBotFollow(int dstX, int dstY, const int divX, const int divY)
Definition: being.cpp:2153
void setWeaponId(const int id)
Definition: being.cpp:3135
void setPixelPositionF(const Vector &pos)
Definition: being.cpp:526
static void searchSlotValueItr(std::vector< int >::iterator &it, int &idx, std::vector< int > &slotRemap, const int val)
Definition: being.cpp:4662
void addEffect(const std::string &name)
Definition: being.cpp:4959
std::string getSitAction() const
Definition: being.cpp:1419
bool mIsGM
Definition: being.h:1235
Text * mText
Definition: being.h:1198
virtual void nextTile()
Definition: being.cpp:1782
void moveBotTo(int dstX, int dstY)
Definition: being.cpp:2192
Party * getParty() const
Definition: being.h:330
void setLanguageId(const int lang)
Definition: being.cpp:5555
void showAwayBadge(const bool show)
Definition: being.cpp:5293
SpriteParticleInfo mSpriteParticles
Definition: being.h:1207
time_t mMoveTime
Definition: being.h:1352
void stopCast(const bool b)
Definition: being.cpp:5522
virtual void setDirection(const uint8_t direction)
Definition: being.cpp:1690
void updateHit(const int amount)
Definition: being.cpp:4686
virtual void setAction(const BeingActionT &action, const int attackId)
Definition: being.cpp:1554
void setTrickDead(const bool b)
Definition: being.cpp:5465
ReachableT mReachable
Definition: being.h:1328
const Guild * getGuild(const std::string &guildName) const
Definition: being.cpp:1258
int mFixedOffsetY
Definition: being.h:1321
void logic()
Definition: being.cpp:1834
int mX
Definition: being.h:1314
static int mSpeechType
Definition: being.h:1333
void showGuildBadge(const bool show)
Definition: being.cpp:1179
void setHairTempSprite(const unsigned int slot, const int id)
Definition: being.cpp:3218
static BeingCacheEntry * getCacheEntry(const BeingId id)
Definition: being.cpp:3559
void drawBeingCursor(Graphics *const graphics, const int offsetX, const int offsetY) const
Definition: being.cpp:3734
int mBadgesX
Definition: being.h:1370
void drawNpcSpriteAt(Graphics *const graphics, const int x, const int y) const
Definition: being.cpp:3998
void setGuildName(const std::string &name)
Definition: being.cpp:1196
void addItemParticlesCards(const int id, const SpriteDisplay &display, const CardsList &cards)
Definition: being.cpp:5081
static bool mHighlightMonsterAttackRange
Definition: being.h:1335
void drawEmotion(Graphics *const graphics, const int offsetX, const int offsetY) const
Definition: being.cpp:2375
~Being()
Definition: being.cpp:337
void setDefaultNameColor(const UserColorIdT defaultColor)
Definition: being.cpp:2639
virtual int getLevel() const
Definition: being.h:604
void drawPortalSpriteAt(Graphics *const graphics, const int x, const int y) const
Definition: being.cpp:4222
void setParty(Party *const party)
Definition: being.cpp:1307
void drawElemental(Graphics *const graphics, const int offsetX, const int offsetY) const
Definition: being.cpp:3816
int mY
Definition: being.h:1315
void showNameBadge(const bool show)
Definition: being.cpp:1119
std::string getAttackAction() const
Definition: being.cpp:1525
static Move mMoveNames
Definition: being.h:1346
virtual void setTileCoords(const int x, const int y)
Definition: being.cpp:5010
void updatePercentHP()
Definition: being.cpp:4876
CastingEffect * mCastingEffect
Definition: being.h:1176
static bool mEnableReorderSprites
Definition: being.h:1344
std::string getWeaponAttackAction(const ItemInfo *const weapon) const
Definition: being.cpp:1452
void drawMercenarySpriteAt(Graphics *const graphics, const int x, const int y) const
Definition: being.cpp:4114
void setClanName(const std::string &name)
Definition: being.cpp:1223
void drawOtherSpriteAt(Graphics *const graphics, const int x, const int y) const
Definition: being.cpp:3991
void showBadges(const bool show)
Definition: being.cpp:5214
int mSpeechTime
Definition: being.h:1217
std::string getMoveAction() const
Definition: being.cpp:1437
const Color * mNameColor
Definition: being.h:1192
void showGmBadge(const bool show)
Definition: being.cpp:3646
SpriteDirection::Type mSpriteDirection
Definition: being.h:1233
AnimatedSprite * mAnimationEffect
Definition: being.h:1175
NextSoundInfo mNextSound
Definition: being.h:954
std::string mSellBoard
Definition: being.h:1305
bool mEnemy
Definition: being.h:1378
bool mErased
Definition: being.h:1377
uint16_t getTeamId() const
Definition: being.h:1082
int * mSpriteHide
Definition: being.h:1301