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-2018 The ManaPlus Developers
6  *
7  * This file is part of The ManaPlus Client.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "being/being.h"
24 
25 #include "actormanager.h"
26 #include "beingequipbackend.h"
27 #include "configuration.h"
28 #include "effectmanager.h"
29 #include "guild.h"
30 #include "itemcolormanager.h"
31 #include "party.h"
32 #include "settings.h"
33 #include "soundmanager.h"
34 #include "text.h"
35 
36 #include "being/beingcacheentry.h"
37 #include "being/beingflag.h"
38 #include "being/beingspeech.h"
39 #include "being/castingeffect.h"
40 #include "being/localplayer.h"
41 #include "being/playerinfo.h"
42 #include "being/playerrelations.h"
43 #include "being/homunculusinfo.h"
44 #include "being/mercenaryinfo.h"
45 
46 #include "const/utils/timer.h"
47 
49 
51 
53 
54 #include "fs/files.h"
55 
56 #include "gui/gui.h"
57 #include "gui/userpalette.h"
58 
59 #include "gui/fonts/font.h"
60 
62 
63 #include "gui/windows/chatwindow.h"
67 
68 #include "net/charserverhandler.h"
69 #include "net/gamehandler.h"
70 #include "net/homunculushandler.h"
71 #include "net/mercenaryhandler.h"
72 #include "net/net.h"
73 #include "net/npchandler.h"
74 #include "net/packetlimiter.h"
75 #include "net/playerhandler.h"
76 #include "net/serverfeatures.h"
77 
78 #include "particle/particleinfo.h"
79 
80 #include "resources/attack.h"
81 #include "resources/chatobject.h"
82 #include "resources/emoteinfo.h"
83 #include "resources/emotesprite.h"
84 #include "resources/horseinfo.h"
85 #include "resources/iteminfo.h"
86 
87 #include "resources/db/avatardb.h"
88 #include "resources/db/badgesdb.h"
89 #include "resources/db/groupdb.h"
91 #include "resources/db/emotedb.h"
93 #include "resources/db/horsedb.h"
96 #include "resources/db/monsterdb.h"
97 #include "resources/db/npcdb.h"
98 #include "resources/db/petdb.h"
100 
101 #include "resources/image/image.h"
102 
103 #include "resources/item/item.h"
104 
105 #include "resources/map/map.h"
106 
109 
111 
113 
114 #include "utils/checkutils.h"
115 #include "utils/delete2.h"
116 #include "utils/foreach.h"
117 #include "utils/gettext.h"
118 #include "utils/likely.h"
119 #include "utils/stdmove.h"
120 #include "utils/timer.h"
121 
122 #include "debug.h"
123 
124 const unsigned int CACHE_SIZE = 50;
125 
126 time_t Being::mUpdateConfigTime = 0;
127 unsigned int Being::mConfLineLim = 0;
128 int Being::mSpeechType = 0;
129 bool Being::mHighlightMapPortals = false;
131 bool Being::mLowTraffic = true;
132 bool Being::mDrawHotKeys = true;
133 bool Being::mShowBattleEvents = false;
134 bool Being::mShowMobHP = false;
135 bool Being::mShowOwnHP = false;
136 bool Being::mShowGender = false;
137 bool Being::mShowLevel = false;
138 bool Being::mShowPlayersStatus = false;
139 bool Being::mEnableReorderSprites = true;
140 bool Being::mHideErased = false;
142 bool Being::mUseDiagonal = true;
144 int Being::mAwayEffect = -1;
146 
147 std::list<BeingCacheEntry*> beingInfoCache;
148 typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
149 typedef std::map<int, int>::const_iterator IntMapCIter;
150 
151 static const unsigned int SPEECH_TIME = 500;
152 static const unsigned int SPEECH_MIN_TIME = 200;
153 static const unsigned int SPEECH_MAX_TIME = 800;
154 
155 #define for_each_badges() \
156  for (int f = 0; f < BadgeIndex::BadgeIndexSize; f++)
157 
158 #define for_each_horses(name) \
159  FOR_EACH (STD_VECTOR<AnimatedSprite*>::const_iterator, it, name)
160 
162  const ActorTypeT type) :
163  ActorSprite(id),
164  mNextSound(),
165  mInfo(BeingInfo::unknown),
166  mEmotionSprite(nullptr),
167  mAnimationEffect(nullptr),
168  mCastingEffect(nullptr),
169  mBadges(),
170  mSpriteAction(SpriteAction::STAND),
171  mName(),
172  mExtName(),
173  mRaceName(),
174  mPartyName(),
175  mGuildName(),
176  mClanName(),
177  mSpeech(),
178  mDispName(nullptr),
179  mNameColor(nullptr),
180  mEquippedWeapon(nullptr),
181  mPath(),
182  mText(nullptr),
183  mTextColor(nullptr),
184  mDest(),
185  mSlots(),
186  mSpriteParticles(),
187  mGuilds(),
188  mParty(nullptr),
189  mActionTime(0),
190  mEmotionTime(0),
191  mSpeechTime(0),
192  mAttackSpeed(350),
193  mLevel(0),
194  mGroupId(0),
195  mAttackRange(1),
196  mLastAttackX(0),
197  mLastAttackY(0),
198  mPreStandTime(0),
199  mGender(Gender::UNSPECIFIED),
200  mAction(BeingAction::STAND),
201  mSubType(fromInt(0xFFFF, BeingTypeId)),
202  mDirection(BeingDirection::DOWN),
203  mDirectionDelayed(0),
204  mSpriteDirection(SpriteDirection::DOWN),
205  mShowName(false),
206  mIsGM(false),
207  mType(type),
208  mSpeechBubble(nullptr),
209  mWalkSpeed(playerHandler != nullptr ?
210  playerHandler->getDefaultWalkSpeed() : 1),
211  mSpeed(playerHandler != nullptr ?
212  playerHandler->getDefaultWalkSpeed() : 1),
213  mIp(),
214  mSpriteRemap(new int[20]),
215  mSpriteHide(new int[20]),
216  mSpriteDraw(new int[20]),
217  mComment(),
218  mBuyBoard(),
219  mSellBoard(),
220  mOwner(nullptr),
221  mSpecialParticle(nullptr),
222  mChat(nullptr),
223  mHorseInfo(nullptr),
224  mDownHorseSprites(),
225  mUpHorseSprites(),
226  mSpiritParticles(),
227  mX(0),
228  mY(0),
229  mCachedX(0),
230  mCachedY(0),
231  mSortOffsetY(0),
232  mPixelOffsetY(0),
233  mFixedOffsetY(0),
234  mOldHeight(0),
235  mDamageTaken(0),
236  mHP(0),
237  mMaxHP(0),
238  mDistance(0),
239  mReachable(Reachable::REACH_UNKNOWN),
240  mGoodStatus(-1),
241  mMoveTime(0),
242  mAttackTime(0),
243  mTalkTime(0),
244  mOtherTime(0),
245  mTestTime(cur_time),
246  mAttackDelay(0),
247  mMinHit(0),
248  mMaxHit(0),
249  mCriticalHit(0),
250  mPvpRank(0),
251  mNumber(100),
252  mSpiritBalls(0U),
253  mUsageCounter(1),
254  mKarma(0),
255  mManner(0),
256  mAreaSize(11),
257  mCastEndTime(0),
258  mLanguageId(-1),
259  mBadgesX(0),
260  mBadgesY(0),
261  mCreatorId(BeingId_zero),
262  mTeamId(0U),
263  mLook(0U),
264  mBadgesCount(0U),
265  mHairColor(ItemColor_zero),
266  mErased(false),
267  mEnemy(false),
268  mGotComment(false),
269  mAdvanced(false),
270  mShop(false),
271  mAway(false),
272  mInactive(false),
273  mNeedPosUpdate(true),
274  mBotAi(true),
275  mAllowNpcEquipment(false)
276 {
277  for (int f = 0; f < 20; f ++)
278  {
279  mSpriteRemap[f] = f;
280  mSpriteHide[f] = 0;
281  mSpriteDraw[f] = 0;
282  }
283 
285  mBadges[f] = nullptr;
286 }
287 
288 void Being::postInit(const BeingTypeId subtype,
289  Map *const map)
290 {
291  setMap(map);
292  setSubtype(subtype, 0);
293 
295 
296  switch (mType)
297  {
298  case ActorType::Player:
300  case ActorType::Pet:
303  showName1 = static_cast<VisibleName::Type>(
304  config.getIntValue("visiblenames"));
305  break;
306  case ActorType::Portal:
308  showName1 = VisibleName::Hide;
309  break;
310  default:
311  case ActorType::Unknown:
312  case ActorType::Npc:
313  case ActorType::Monster:
315  case ActorType::Avatar:
316  break;
317  }
318 
319  if (mType != ActorType::Npc)
320  mGotComment = true;
321 
322  config.addListener("visiblenames", this);
323 
324  reReadConfig();
325 
326  if (mType == ActorType::Npc ||
327  showName1 == VisibleName::Show)
328  {
329  setShowName(true);
330  }
331 
332  updateColors();
333  updatePercentHP();
334 }
335 
337 {
338  config.removeListener("visiblenames", this);
340 
341  delete [] mSpriteRemap;
342  mSpriteRemap = nullptr;
343  delete [] mSpriteHide;
344  mSpriteHide = nullptr;
345  delete [] mSpriteDraw;
346  mSpriteDraw = nullptr;
347 
349  delete2(mBadges[f]);
350 
353  delete2(mText);
357  mBadgesCount = 0;
358  delete2(mChat);
359  removeHorse();
360 
362  mSpiritParticles.clear();
363 }
364 
366 {
368 }
369 
370 void Being::setSubtype(const BeingTypeId subtype,
371  const uint16_t look) restrict2
372 {
373  if (mInfo == nullptr)
374  return;
375 
376  if (subtype == mSubType && mLook == look)
377  return;
378 
379  mSubType = subtype;
380  mLook = look;
381 
382  switch (mType)
383  {
384  case ActorType::Monster:
386  if (mInfo != nullptr)
387  {
388  setName(mInfo->getName());
394  }
395  break;
396  case ActorType::Pet:
398  if (mInfo != nullptr)
399  {
400  setName(mInfo->getName());
406  }
407  break;
410  if (mInfo != nullptr)
411  {
412  setName(mInfo->getName());
418  }
419  break;
422  if (mInfo != nullptr)
423  {
424  setName(mInfo->getName());
430  }
431  break;
434  if (mInfo != nullptr)
435  {
436  setName(mInfo->getName());
442  }
443  break;
446  if (mInfo != nullptr)
447  {
448  setName(mInfo->getName());
454  }
455  break;
456  case ActorType::Npc:
458  if (mInfo != nullptr)
459  {
463  std::string());
466  }
467  break;
468  case ActorType::Avatar:
470  if (mInfo != nullptr)
471  {
475  std::string());
476  }
477  break;
478  case ActorType::Player:
479  {
480  int id = -100 - toInt(subtype, int);
481  // Prevent showing errors when sprite doesn't exist
482  if (!ItemDB::exists(id))
483  {
484  id = -100;
485  // TRANSLATORS: default race name
486  setRaceName(_("Human"));
487  if (charServerHandler != nullptr)
488  {
490  id);
491  }
492  }
493  else
494  {
495  const ItemInfo &restrict info = ItemDB::get(id);
496  setRaceName(info.getName());
497  if (charServerHandler != nullptr)
498  {
500  id,
501  info.getColor(fromInt(mLook, ItemColor)));
502  }
503  }
504  break;
505  }
506  case ActorType::Portal:
507  break;
508  case ActorType::Unknown:
510  default:
511  reportAlways("Wrong being type %d in setSubType",
512  CAST_S32(mType));
513  break;
514  }
515 }
516 
518 {
519  if (mInfo == nullptr)
521 
522  return mInfo->getTargetCursorSize();
523 }
524 
526 {
528 
529  updateCoords();
530 
531  if (mText != nullptr)
532  {
533  mText->adviseXY(CAST_S32(pos.x),
534  CAST_S32(pos.y) - getHeight() - mText->getHeight() - 9,
535  mMoveNames);
536  }
537 }
538 
539 void Being::setDestination(const int dstX,
540  const int dstY) restrict2
541 {
542  if (mMap == nullptr)
543  return;
544 
546  mY,
547  dstX,
548  dstY,
550  20));
551 }
552 
554 {
555  mPath.clear();
556 }
557 
559 {
560  mPath = path;
561  if (mPath.empty())
562  return;
563 
565  {
566  nextTile();
568  }
569 }
570 
571 void Being::setSpeech(const std::string &restrict text) restrict2
572 {
573  if (userPalette == nullptr)
574  return;
575 
576  // Remove colors
577  mSpeech = removeColors(text);
578 
579  // Trim whitespace
580  trim(mSpeech);
581 
582  const unsigned int lineLim = mConfLineLim;
583  if (lineLim > 0 && mSpeech.length() > lineLim)
584  mSpeech = mSpeech.substr(0, lineLim);
585 
586  trim(mSpeech);
587  if (mSpeech.empty())
588  return;
589 
590  const size_t sz = mSpeech.size();
591  int time = 0;
592  if (sz < 200)
593  time = CAST_S32(SPEECH_TIME - 300 + (3 * sz));
594 
595  if (time < CAST_S32(SPEECH_MIN_TIME))
596  time = CAST_S32(SPEECH_MIN_TIME);
597 
598  // Check for links
599  size_t start = mSpeech.find('[');
600  size_t e = mSpeech.find(']', start);
601 
602  while (start != std::string::npos && e != std::string::npos)
603  {
604  // Catch multiple embeds and ignore them so it doesn't crash the client.
605  while ((mSpeech.find('[', start + 1) != std::string::npos) &&
606  (mSpeech.find('[', start + 1) < e))
607  {
608  start = mSpeech.find('[', start + 1);
609  }
610 
611  size_t position = mSpeech.find('|');
612  if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
613  {
614  mSpeech.erase(e, 1);
615  mSpeech.erase(start, (position - start) + 1);
616  }
617  position = mSpeech.find("@@");
618 
619  while (position != std::string::npos)
620  {
621  mSpeech.erase(position, 2);
622  position = mSpeech.find('@');
623  }
624 
625  start = mSpeech.find('[', start + 1);
626  e = mSpeech.find(']', start);
627  }
628 
629  if (!mSpeech.empty())
630  {
631  mSpeechTime = time <= CAST_S32(SPEECH_MAX_TIME)
632  ? time : CAST_S32(SPEECH_MAX_TIME);
633  }
634 
635  const int speech = mSpeechType;
636  if (speech == BeingSpeech::TEXT_OVERHEAD)
637  {
638  delete mText;
639  mText = nullptr;
640  mText = new Text(mSpeech,
641  mPixelX,
642  mPixelY - getHeight(),
645  Speech_true,
646  nullptr);
648  (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
649  mMoveNames);
650  }
651  else
652  {
653  const bool isShowName = (speech == BeingSpeech::NAME_IN_BUBBLE);
654  if (mSpeechBubble == nullptr)
656  if (mSpeechBubble != nullptr)
657  {
658  mSpeechBubble->setCaption(isShowName ? mName : "",
661  mSpeechBubble->setText(mSpeech, isShowName);
662  }
663  }
664 }
665 
666 void Being::takeDamage(Being *restrict const attacker,
667  const int amount,
668  const AttackTypeT type,
669  const int attackId,
670  const int level) restrict2
671 {
672  if (userPalette == nullptr || attacker == nullptr)
673  return;
674 
675  BLOCK_START("Being::takeDamage1")
676 
677  Font *font = nullptr;
678  const Color *color;
679 
680  if (gui != nullptr)
681  font = gui->getInfoParticleFont();
682 
683  // Selecting the right color
684  if (type == AttackType::CRITICAL || type == AttackType::FLEE)
685  {
686  if (type == AttackType::CRITICAL)
687  attacker->setCriticalHit(amount);
688 
689  if (attacker == localPlayer)
690  {
691  color = &userPalette->getColor(
693  255U);
694  }
695  else
696  {
698  255U);
699  }
700  }
701  else if (amount == 0)
702  {
703  if (attacker == localPlayer)
704  {
705  // This is intended to be the wrong direction to visually
706  // differentiate between hits and misses
708  255U);
709  }
710  else
711  {
713  255U);
714  }
715  }
716  else if (mType == ActorType::Monster ||
718  mType == ActorType::Pet ||
721  {
722  if (attacker == localPlayer)
723  {
724  color = &userPalette->getColor(
726  255U);
727  }
728  else
729  {
730  color = &userPalette->getColor(
732  255U);
733  }
734  }
735  else if (mType == ActorType::Player &&
736  attacker != localPlayer &&
737  this == localPlayer)
738  {
739  // here player was attacked by other player. mark him as enemy.
741  255U);
742  attacker->setEnemy(true);
743  attacker->updateColors();
744  }
745  else
746  {
748  255U);
749  }
750 
751  if (chatWindow != nullptr && mShowBattleEvents)
752  {
753  if (this == localPlayer)
754  {
755  if (attacker->mType == ActorType::Player || (amount != 0))
756  {
757  ChatWindow::battleChatLog(strprintf("%s : Hit you -%d",
758  attacker->getName().c_str(), amount),
762  }
763  }
764  else if (attacker == localPlayer && (amount != 0))
765  {
766  ChatWindow::battleChatLog(strprintf("%s : You hit %s -%d",
767  attacker->mName.c_str(),
768  mName.c_str(),
769  amount),
773  }
774  }
775  if (font != nullptr && particleEngine != nullptr)
776  {
777  const std::string damage = amount != 0 ? toString(amount) :
778  // TRANSLATORS: dodge or miss message in attacks
779  type == AttackType::FLEE ? _("dodge") : _("miss");
780  // Show damage number
782  mPixelX,
783  mPixelY - 16,
784  color,
785  font,
786  true);
787  }
788  BLOCK_END("Being::takeDamage1")
789  BLOCK_START("Being::takeDamage2")
790 
791  if (type != AttackType::SKILL)
792  attacker->updateHit(amount);
793 
794  if (amount > 0)
795  {
796  if ((localPlayer != nullptr) && localPlayer == this)
797  localPlayer->setLastHitFrom(attacker->mName);
798 
799  mDamageTaken += amount;
800  if (mInfo != nullptr)
801  {
803  this,
804  false,
805  mX,
806  mY);
807 
808  if (!mInfo->isStaticMaxHP())
809  {
810  if ((mHP == 0) && mInfo->getMaxHP() < mDamageTaken)
812  }
813  }
814  if ((mHP != 0) && isAlive())
815  {
816  mHP -= amount;
817  if (mHP < 0)
818  mHP = 0;
819  }
820 
821  if (mType == ActorType::Monster)
822  {
823  updatePercentHP();
824  updateName();
825  }
826  else if (mType == ActorType::Player &&
827  (socialWindow != nullptr) &&
828  !mName.empty())
829  {
831  }
832 
833  if (effectManager != nullptr)
834  {
835  const int hitEffectId = getHitEffect(attacker,
836  type,
837  attackId,
838  level);
839  if (hitEffectId >= 0)
840  effectManager->trigger(hitEffectId, this, 0);
841  }
842  }
843  else
844  {
845  if (effectManager != nullptr)
846  {
847  int hitEffectId = -1;
848  if (type == AttackType::SKILL)
849  {
850  hitEffectId = getHitEffect(attacker,
852  attackId,
853  level);
854  }
855  else
856  {
857  hitEffectId = getHitEffect(attacker,
859  attackId,
860  level);
861  }
862  if (hitEffectId >= 0)
863  effectManager->trigger(hitEffectId, this, 0);
864  }
865  }
866  BLOCK_END("Being::takeDamage2")
867 }
868 
869 int Being::getHitEffect(const Being *restrict const attacker,
870  const AttackTypeT type,
871  const int attackId,
872  const int level) const restrict2
873 {
874  if (effectManager == nullptr)
875  return 0;
876 
877  BLOCK_START("Being::getHitEffect")
878  // Init the particle effect path based on current
879  // weapon or default.
880  int hitEffectId = 0;
881  if (type == AttackType::SKILL || type == AttackType::SKILLMISS)
882  {
883  const SkillData *restrict const data =
884  skillDialog->getSkillDataByLevel(attackId, level);
885  if (data == nullptr)
886  return -1;
887  if (type == AttackType::SKILL)
888  {
889  hitEffectId = data->hitEffectId;
890  if (hitEffectId == -1)
891  hitEffectId = paths.getIntValue("skillHitEffectId");
892  }
893  else
894  {
895  hitEffectId = data->missEffectId;
896  if (hitEffectId == -1)
897  hitEffectId = paths.getIntValue("skillMissEffectId");
898  }
899  }
900  else
901  {
902  if (attacker != nullptr)
903  {
904  const ItemInfo *restrict const attackerWeapon
905  = attacker->getEquippedWeapon();
906  if (attackerWeapon != nullptr &&
907  attacker->getType() == ActorType::Player)
908  {
909  if (type == AttackType::MISS)
910  hitEffectId = attackerWeapon->getMissEffectId();
911  else if (type != AttackType::CRITICAL)
912  hitEffectId = attackerWeapon->getHitEffectId();
913  else
914  hitEffectId = attackerWeapon->getCriticalHitEffectId();
915  }
916  else if (attacker->getType() == ActorType::Monster)
917  {
918  const BeingInfo *restrict const info = attacker->getInfo();
919  if (info != nullptr)
920  {
921  const Attack *restrict const atk =
922  info->getAttack(attackId);
923  if (atk != nullptr)
924  {
925  if (type == AttackType::MISS)
926  hitEffectId = atk->mMissEffectId;
927  else if (type != AttackType::CRITICAL)
928  hitEffectId = atk->mHitEffectId;
929  else
930  hitEffectId = atk->mCriticalHitEffectId;
931  }
932  else
933  {
934  hitEffectId = getDefaultEffectId(type);
935  }
936  }
937  }
938  else
939  {
940  hitEffectId = getDefaultEffectId(type);
941  }
942  }
943  else
944  {
945  hitEffectId = getDefaultEffectId(type);
946  }
947  }
948  BLOCK_END("Being::getHitEffect")
949  return hitEffectId;
950 }
951 
953 {
954  if (type == AttackType::MISS)
955  return paths.getIntValue("missEffectId");
956  else if (type != AttackType::CRITICAL)
957  return paths.getIntValue("hitEffectId");
958  else
959  return paths.getIntValue("criticalHitEffectId");
960 }
961 
962 void Being::handleAttack(Being *restrict const victim,
963  const int damage,
964  const int attackId) restrict2
965 {
966  if ((victim == nullptr) || (mInfo == nullptr))
967  return;
968 
969  BLOCK_START("Being::handleAttack")
970 
971  if (this != localPlayer)
972  setAction(BeingAction::ATTACK, attackId);
973 
974  mLastAttackX = victim->mX;
975  mLastAttackY = victim->mY;
976 
977  if (mType == ActorType::Player && (mEquippedWeapon != nullptr))
979  else if (mInfo->getAttack(attackId) != nullptr)
980  fireMissile(victim, mInfo->getAttack(attackId)->mMissile);
981 
982  reset();
984 
986  this != localPlayer)
987  {
988  const uint8_t dir = calcDirection(victim->mX,
989  victim->mY);
990  if (dir != 0u)
991  setDirection(dir);
992  }
993 
994  if ((damage != 0) && victim->mType == ActorType::Player
995  && victim->mAction == BeingAction::SIT)
996  {
997  victim->setAction(BeingAction::STAND, 0);
998  }
999 
1000  if (mType == ActorType::Player)
1001  {
1002  if (mSlots.size() >= 10)
1003  {
1004  // here 10 is weapon slot
1005  int weaponId = mSlots[10].spriteId;
1006  if (weaponId == 0)
1007  weaponId = -100 - toInt(mSubType, int);
1008  const ItemInfo &info = ItemDB::get(weaponId);
1009  playSfx(info.getSound(
1010  (damage > 0) ? ItemSoundEvent::HIT : ItemSoundEvent::MISS),
1011  victim,
1012  true,
1013  mX, mY);
1014  }
1015  }
1016  else
1017  {
1018  playSfx(mInfo->getSound((damage > 0) ?
1019  ItemSoundEvent::HIT : ItemSoundEvent::MISS), victim, true, mX, mY);
1020  }
1021  BLOCK_END("Being::handleAttack")
1022 }
1023 
1025  const int skillId,
1026  const int skillLevel) restrict2
1027 {
1028  if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1029  return;
1030 
1031  setAction(BeingAction::CAST, skillId);
1032 
1034  skillId,
1035  skillLevel);
1036 
1037  if (data != nullptr)
1038  {
1039  effectManager->triggerDefault(data->castingSrcEffectId,
1040  this,
1041  paths.getIntValue("skillCastingSrcEffectId"));
1042  effectManager->triggerDefault(data->castingDstEffectId,
1043  victim,
1044  paths.getIntValue("skillCastingDstEffectId"));
1045  fireMissile(victim, data->castingMissile);
1046  }
1047 }
1048 
1049 void Being::handleSkill(Being *restrict const victim,
1050  const int damage,
1051  const int skillId,
1052  const int skillLevel) restrict2
1053 {
1054  if ((victim == nullptr) || (mInfo == nullptr) || (skillDialog == nullptr))
1055  return;
1056 
1057  const SkillInfo *restrict const skill = skillDialog->getSkill(skillId);
1058  const SkillData *restrict const data = skill != nullptr
1059  ? skill->getData1(skillLevel) : nullptr;
1060  if (data != nullptr)
1061  {
1062  effectManager->triggerDefault(data->srcEffectId,
1063  this,
1064  paths.getIntValue("skillSrcEffectId"));
1065  effectManager->triggerDefault(data->dstEffectId,
1066  victim,
1067  paths.getIntValue("skillDstEffectId"));
1068  fireMissile(victim, data->missile);
1069  }
1070 
1071  if (this != localPlayer && (skill != nullptr))
1072  {
1073  const SkillType::SkillType type = skill->type;
1074  if ((type & SkillType::Attack) != 0 ||
1075  (type & SkillType::Ground) != 0)
1076  {
1078  }
1079  else
1080  {
1082  }
1083  }
1084 
1085  reset();
1087 
1089  this != localPlayer)
1090  {
1091  const uint8_t dir = calcDirection(victim->mX,
1092  victim->mY);
1093  if (dir != 0u)
1094  setDirection(dir);
1095  }
1096  if ((damage != 0) && victim->mType == ActorType::Player
1097  && victim->mAction == BeingAction::SIT)
1098  {
1099  victim->setAction(BeingAction::STAND, 0);
1100  }
1101  if (data != nullptr)
1102  {
1103  if (damage > 0)
1104  playSfx(data->soundHit, victim, true, mX, mY);
1105  else
1106  playSfx(data->soundMiss, victim, true, mX, mY);
1107  }
1108  else
1109  {
1110  playSfx(mInfo->getSound((damage > 0) ?
1112  victim,
1113  true,
1114  mX, mY);
1115  }
1116 }
1117 
1118 void Being::showNameBadge(const bool show) restrict2
1119 {
1121  if (show &&
1122  !mName.empty() &&
1124  {
1125  const std::string badge = BadgesDB::getNameBadge(mName);
1126  if (!badge.empty())
1127  {
1129  paths.getStringValue("badges") + badge,
1130  0);
1131  }
1132  }
1133 }
1134 
1135 void Being::setName(const std::string &restrict name) restrict2
1136 {
1137  mExtName = name;
1138  if (mType == ActorType::Npc)
1139  {
1140  mName = name.substr(0, name.find('#', 0));
1141  showName();
1142  }
1143  else if (mType == ActorType::Player)
1144  {
1145  if (mName != name)
1146  {
1147  mName = name;
1148  showNameBadge(!mName.empty());
1149  }
1150  if (getShowName())
1151  showName();
1152  }
1153  else
1154  {
1155  if (mType == ActorType::Portal)
1156  mName = name.substr(0, name.find('#', 0));
1157  else
1158  mName = name;
1159 
1160  if (getShowName())
1161  showName();
1162  }
1163 }
1164 
1165 void Being::setShowName(const bool doShowName) restrict2
1166 {
1167  if (mShowName == doShowName)
1168  return;
1169 
1170  mShowName = doShowName;
1171 
1172  if (doShowName)
1173  showName();
1174  else
1176 }
1177 
1178 void Being::showGuildBadge(const bool show) restrict2
1179 {
1181  if (show &&
1182  !mGuildName.empty() &&
1184  {
1185  const std::string badge = BadgesDB::getGuildBadge(mGuildName);
1186  if (!badge.empty())
1187  {
1189  paths.getStringValue("badges") + badge,
1190  0);
1191  }
1192  }
1193 }
1194 
1195 void Being::setGuildName(const std::string &restrict name) restrict2
1196 {
1197  if (mGuildName != name)
1198  {
1199  mGuildName = name;
1200  showGuildBadge(!mGuildName.empty());
1202  }
1203 }
1204 
1205 void Being::showClanBadge(const bool show) restrict2
1206 {
1208  if (show &&
1209  !mClanName.empty() &&
1211  {
1212  const std::string badge = BadgesDB::getClanBadge(mClanName);
1213  if (!badge.empty())
1214  {
1216  paths.getStringValue("badges") + badge,
1217  0);
1218  }
1219  }
1220 }
1221 
1222 void Being::setClanName(const std::string &restrict name) restrict2
1223 {
1224  if (mClanName != name)
1225  {
1226  mClanName = name;
1227  showClanBadge(!mClanName.empty());
1229  }
1230 }
1231 
1232 void Being::setGuildPos(const std::string &restrict pos A_UNUSED) restrict2
1233 {
1234 }
1235 
1237 {
1238  if (guild == nullptr)
1239  return;
1240 
1241  mGuilds[guild->getId()] = guild;
1242 
1243  if (this == localPlayer && (socialWindow != nullptr))
1245 }
1246 
1247 void Being::removeGuild(const int id) restrict2
1248 {
1249  if (this == localPlayer && (socialWindow != nullptr))
1251 
1252  if (mGuilds[id] != nullptr)
1253  mGuilds[id]->removeMember(mName);
1254  mGuilds.erase(id);
1255 }
1256 
1257 const Guild *Being::getGuild(const std::string &restrict guildName) const
1258  restrict2
1259 {
1261  {
1262  const Guild *restrict const guild = itr->second;
1263  if ((guild != nullptr) && guild->getName() == guildName)
1264  return guild;
1265  }
1266 
1267  return nullptr;
1268 }
1269 
1270 const Guild *Being::getGuild(const int id) const restrict2
1271 {
1272  const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
1273  if (itr != mGuilds.end())
1274  return itr->second;
1275 
1276  return nullptr;
1277 }
1278 
1280 {
1281  const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
1282  if (itr != mGuilds.end())
1283  return itr->second;
1284 
1285  return nullptr;
1286 }
1287 
1289 {
1291  {
1292  Guild *const guild = itr->second;
1293 
1294  if (guild != nullptr)
1295  {
1296  if (this == localPlayer && (socialWindow != nullptr))
1297  socialWindow->removeTab(guild);
1298 
1299  guild->removeMember(mId);
1300  }
1301  }
1302 
1303  mGuilds.clear();
1304 }
1305 
1307 {
1308  if (party == mParty)
1309  return;
1310 
1311  Party *const old = mParty;
1312  mParty = party;
1313 
1314  if (old != nullptr)
1315  old->removeMember(mId);
1316 
1317  if (party != nullptr)
1318  party->addMember(mId, mName);
1319 
1320  updateColors();
1321 
1322  if (this == localPlayer && (socialWindow != nullptr))
1323  {
1324  if (old != nullptr)
1325  socialWindow->removeTab(old);
1326 
1327  if (party != nullptr)
1329  }
1330 }
1331 
1333 {
1334  if (localPlayer == nullptr)
1335  return;
1336 
1337  Guild *restrict const guild = localPlayer->getGuild();
1338  if (guild == nullptr)
1339  {
1340  clearGuilds();
1341  updateColors();
1342  return;
1343  }
1344  if (guild->getMember(mName) != nullptr)
1345  {
1346  setGuild(guild);
1347  if (!guild->getName().empty())
1348  setGuildName(guild->getName());
1349  }
1350  updateColors();
1351 }
1352 
1354 {
1355  Guild *restrict const old = getGuild();
1356  if (guild == old)
1357  return;
1358 
1359  clearGuilds();
1360  addGuild(guild);
1361 
1362  if (old != nullptr)
1363  old->removeMember(mName);
1364 
1365  updateColors();
1366 
1367  if (this == localPlayer && (socialWindow != nullptr))
1368  {
1369  if (old != nullptr)
1370  socialWindow->removeTab(old);
1371 
1372  if (guild != nullptr)
1374  }
1375 }
1376 
1377 void Being::fireMissile(Being *restrict const victim,
1378  const MissileInfo &restrict missile) const restrict2
1379 {
1380  BLOCK_START("Being::fireMissile")
1381 
1382  if (victim == nullptr ||
1383  missile.particle.empty() ||
1384  particleEngine == nullptr)
1385  {
1386  BLOCK_END("Being::fireMissile")
1387  return;
1388  }
1389 
1390  Particle *restrict const target = particleEngine->createChild();
1391 
1392  if (target == nullptr)
1393  {
1394  BLOCK_END("Being::fireMissile")
1395  return;
1396  }
1397 
1398  // +++ add z particle position?
1399  Particle *restrict const missileParticle = target->addEffect(
1400  missile.particle,
1401  mPixelX,
1402  mPixelY,
1403  0);
1404 
1405  target->moveBy(Vector(0.0F, 0.0F, missile.z));
1406  target->setLifetime(missile.lifeTime);
1407  victim->controlAutoParticle(target);
1408 
1409  if (missileParticle != nullptr)
1410  {
1411  missileParticle->setDestination(target, missile.speed, 0.0F);
1412  missileParticle->setDieDistance(missile.dieDistance);
1413  missileParticle->setLifetime(missile.lifeTime);
1414  }
1415  BLOCK_END("Being::fireMissile")
1416 }
1417 
1418 std::string Being::getSitAction() const restrict2
1419 {
1420  if (mHorseId != 0)
1421  return SpriteAction::SITRIDE;
1422  if (mMap != nullptr)
1423  {
1424  const unsigned char mask = mMap->getBlockMask(mX, mY);
1425  if ((mask & BlockMask::GROUNDTOP) != 0)
1426  return SpriteAction::SITTOP;
1427  else if ((mask & BlockMask::AIR) != 0)
1428  return SpriteAction::SITSKY;
1429  else if ((mask & BlockMask::WATER) != 0)
1430  return SpriteAction::SITWATER;
1431  }
1432  return SpriteAction::SIT;
1433 }
1434 
1435 
1436 std::string Being::getMoveAction() const restrict2
1437 {
1438  if (mHorseId != 0)
1439  return SpriteAction::RIDE;
1440  if (mMap != nullptr)
1441  {
1442  const unsigned char mask = mMap->getBlockMask(mX, mY);
1443  if ((mask & BlockMask::AIR) != 0)
1444  return SpriteAction::FLY;
1445  else if ((mask & BlockMask::WATER) != 0)
1446  return SpriteAction::SWIM;
1447  }
1448  return SpriteAction::MOVE;
1449 }
1450 
1451 std::string Being::getWeaponAttackAction(const ItemInfo *restrict const weapon)
1452  const restrict2
1453 {
1454  if (weapon == nullptr)
1455  return getAttackAction();
1456 
1457  if (mHorseId != 0)
1458  return weapon->getRideAttackAction();
1459  if (mMap != nullptr)
1460  {
1461  const unsigned char mask = mMap->getBlockMask(mX, mY);
1462  if ((mask & BlockMask::AIR) != 0)
1463  return weapon->getSkyAttackAction();
1464  else if ((mask & BlockMask::WATER) != 0)
1465  return weapon->getWaterAttackAction();
1466  }
1467  return weapon->getAttackAction();
1468 }
1469 
1470 std::string Being::getAttackAction(const Attack *restrict const attack1) const
1471  restrict2
1472 {
1473  if (attack1 == nullptr)
1474  return getAttackAction();
1475 
1476  if (mHorseId != 0)
1477  return attack1->mRideAction;
1478  if (mMap != nullptr)
1479  {
1480  const unsigned char mask = mMap->getBlockMask(mX, mY);
1481  if ((mask & BlockMask::AIR) != 0)
1482  return attack1->mSkyAction;
1483  else if ((mask & BlockMask::WATER) != 0)
1484  return attack1->mWaterAction;
1485  }
1486  return attack1->mAction;
1487 }
1488 
1489 std::string Being::getCastAction(const SkillInfo *restrict const skill) const
1490  restrict2
1491 {
1492  if (skill == nullptr)
1493  return getCastAction();
1494 
1495  if (mHorseId != 0)
1496  return skill->castingRideAction;
1497  if (mMap != nullptr)
1498  {
1499  const unsigned char mask = mMap->getBlockMask(mX, mY);
1500  if ((mask & BlockMask::AIR) != 0)
1501  return skill->castingSkyAction;
1502  else if ((mask & BlockMask::WATER) != 0)
1503  return skill->castingWaterAction;
1504  }
1505  return skill->castingAction;
1506 }
1507 
1508 #define getSpriteAction(func, action) \
1509  std::string Being::get##func##Action() const restrict2\
1510 { \
1511  if (mHorseId != 0) \
1512  return SpriteAction::action##RIDE; \
1513  if (mMap) \
1514  { \
1515  const unsigned char mask = mMap->getBlockMask(mX, mY); \
1516  if (mask & BlockMask::AIR) \
1517  return SpriteAction::action##SKY; \
1518  else if (mask & BlockMask::WATER) \
1519  return SpriteAction::action##WATER; \
1520  } \
1521  return SpriteAction::action; \
1522 }
1523 
1528 
1530 {
1531  if (mHorseId != 0)
1532  return SpriteAction::STANDRIDE;
1533  if (mMap != nullptr)
1534  {
1535  const unsigned char mask = mMap->getBlockMask(mX, mY);
1536  if (mTrickDead)
1537  {
1538  if ((mask & BlockMask::AIR) != 0)
1539  return SpriteAction::DEADSKY;
1540  else if ((mask & BlockMask::WATER) != 0)
1541  return SpriteAction::DEADWATER;
1542  else
1543  return SpriteAction::DEAD;
1544  }
1545  if ((mask & BlockMask::AIR) != 0)
1546  return SpriteAction::STANDSKY;
1547  else if ((mask & BlockMask::WATER) != 0)
1548  return SpriteAction::STANDWATER;
1549  }
1550  return SpriteAction::STAND;
1551 }
1552 
1554  const int attackId) restrict2
1555 {
1556  std::string currentAction = SpriteAction::INVALID;
1557 
1558  switch (action)
1559  {
1560  case BeingAction::MOVE:
1561  if (mInfo != nullptr)
1562  {
1564  ItemSoundEvent::MOVE), nullptr, true, mX, mY);
1565  }
1566  currentAction = getMoveAction();
1567  // Note: When adding a run action,
1568  // Differentiate walk and run with action name,
1569  // while using only the ACTION_MOVE.
1570  break;
1571  case BeingAction::SIT:
1572  currentAction = getSitAction();
1573  if (mInfo != nullptr)
1574  {
1575  ItemSoundEvent::Type event;
1576  if (currentAction == SpriteAction::SITTOP)
1577  event = ItemSoundEvent::SITTOP;
1578  else
1579  event = ItemSoundEvent::SIT;
1580  playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
1581  }
1582  break;
1583  case BeingAction::ATTACK:
1584  if (mEquippedWeapon != nullptr)
1585  {
1586  currentAction = getWeaponAttackAction(mEquippedWeapon);
1587  reset();
1588  }
1589  else
1590  {
1591  if (mInfo == nullptr || mInfo->getAttack(attackId) == nullptr)
1592  break;
1593 
1594  currentAction = getAttackAction(mInfo->getAttack(attackId));
1595  reset();
1596 
1597  // attack particle effect
1598  if (ParticleEngine::enabled && (effectManager != nullptr))
1599  {
1600  const int effectId = mInfo->getAttack(attackId)->mEffectId;
1601  if (effectId >= 0)
1602  {
1603  effectManager->triggerDirection(effectId,
1604  this,
1606  }
1607  }
1608  }
1609  break;
1610  case BeingAction::CAST:
1611  if (skillDialog != nullptr)
1612  {
1613  const SkillInfo *restrict const info =
1614  skillDialog->getSkill(attackId);
1615  currentAction = getCastAction(info);
1616  }
1617  break;
1618  case BeingAction::HURT:
1619  if (mInfo != nullptr)
1620  {
1622  this, false, mX, mY);
1623  }
1624  break;
1625  case BeingAction::DEAD:
1626  currentAction = getDeadAction();
1627  if (mInfo != nullptr)
1628  {
1630  this,
1631  false,
1632  mX, mY);
1633  if (mType == ActorType::Monster ||
1634  mType == ActorType::Npc ||
1636  {
1638  }
1639  }
1640  break;
1641  case BeingAction::STAND:
1642  currentAction = getStandAction();
1643  break;
1644  case BeingAction::SPAWN:
1645  if (mInfo != nullptr)
1646  {
1648  nullptr, true, mX, mY);
1649  }
1650  currentAction = getSpawnAction();
1651  break;
1652  case BeingAction::PRESTAND:
1653  break;
1654  default:
1655  logger->log("Being::setAction unknown action: "
1656  + toString(CAST_U32(action)));
1657  break;
1658  }
1659 
1660  if (currentAction != SpriteAction::INVALID)
1661  {
1662  mSpriteAction = currentAction;
1663  play(currentAction);
1664  if (mEmotionSprite != nullptr)
1665  mEmotionSprite->play(currentAction);
1666  if (mAnimationEffect != nullptr)
1667  mAnimationEffect->play(currentAction);
1668  for_each_badges()
1669  {
1670  AnimatedSprite *const sprite = mBadges[f];
1671  if (sprite != nullptr)
1672  sprite->play(currentAction);
1673  }
1675  (*it)->play(currentAction);
1677  (*it)->play(currentAction);
1678  mAction = action;
1679  }
1680 
1681  if (currentAction != SpriteAction::MOVE
1682  && currentAction != SpriteAction::FLY
1683  && currentAction != SpriteAction::SWIM)
1684  {
1686  }
1687 }
1688 
1689 void Being::setDirection(const uint8_t direction) restrict2
1690 {
1691  if (mDirection == direction)
1692  return;
1693 
1694  mDirection = direction;
1695 
1696  mDirectionDelayed = 0;
1697 
1698  // if the direction does not change much, keep the common component
1699  int mFaceDirection = mDirection & direction;
1700  if (mFaceDirection == 0)
1701  mFaceDirection = direction;
1702 
1704  if ((mFaceDirection & BeingDirection::UP) != 0)
1705  {
1706  if ((mFaceDirection & BeingDirection::LEFT) != 0)
1708  else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1710  else
1711  dir = SpriteDirection::UP;
1712  }
1713  else if ((mFaceDirection & BeingDirection::DOWN) != 0)
1714  {
1715  if ((mFaceDirection & BeingDirection::LEFT) != 0)
1717  else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1719  else
1720  dir = SpriteDirection::DOWN;
1721  }
1722  else if ((mFaceDirection & BeingDirection::RIGHT) != 0)
1723  {
1724  dir = SpriteDirection::RIGHT;
1725  }
1726  else
1727  {
1728  dir = SpriteDirection::LEFT;
1729  }
1730  mSpriteDirection = dir;
1731 
1733  if (mEmotionSprite != nullptr)
1735  if (mAnimationEffect != nullptr)
1737 
1738  for_each_badges()
1739  {
1740  AnimatedSprite *const sprite = mBadges[f];
1741  if (sprite != nullptr)
1742  sprite->setSpriteDirection(dir);
1743  }
1744 
1746  (*it)->setSpriteDirection(dir);
1748  (*it)->setSpriteDirection(dir);
1750 }
1751 
1753 {
1754  uint8_t dir = 0;
1755  if (mDest.x > mX)
1756  dir |= BeingDirection::RIGHT;
1757  else if (mDest.x < mX)
1758  dir |= BeingDirection::LEFT;
1759  if (mDest.y > mY)
1760  dir |= BeingDirection::DOWN;
1761  else if (mDest.y < mY)
1762  dir |= BeingDirection::UP;
1763  return dir;
1764 }
1765 
1766 uint8_t Being::calcDirection(const int dstX,
1767  const int dstY) const restrict2
1768 {
1769  uint8_t dir = 0;
1770  if (dstX > mX)
1771  dir |= BeingDirection::RIGHT;
1772  else if (dstX < mX)
1773  dir |= BeingDirection::LEFT;
1774  if (dstY > mY)
1775  dir |= BeingDirection::DOWN;
1776  else if (dstY < mY)
1777  dir |= BeingDirection::UP;
1778  return dir;
1779 }
1780 
1782 {
1783  if (mPath.empty())
1784  {
1787  return;
1788  }
1789 
1790  const Position pos = mPath.front();
1791  mPath.pop_front();
1792 
1793  const uint8_t dir = calcDirection(pos.x, pos.y);
1794  if (dir != 0u)
1795  setDirection(dir);
1796 
1797  if (mMap == nullptr ||
1798  !mMap->getWalk(pos.x, pos.y, getBlockWalkMask()))
1799  {
1801  return;
1802  }
1803 
1804  mActionTime += mSpeed / 10;
1806  && mX != pos.x && mY != pos.y)
1807  {
1808  mSpeed = mWalkSpeed * 14 / 10;
1809  }
1810  else
1811  {
1812  mSpeed = mWalkSpeed;
1813  }
1814 
1815  if (mX != pos.x || mY != pos.y)
1816  {
1819  mMap->getBlockMask(mX, mY) != mMap->getBlockMask(pos.x, pos.y))
1820  {
1822  }
1823  }
1824  mX = pos.x;
1825  mY = pos.y;
1826  const uint8_t height = mMap->getHeightOffset(mX, mY);
1827  mPixelOffsetY = height - mOldHeight;
1828  mFixedOffsetY = height;
1829  mNeedPosUpdate = true;
1831 }
1832 
1834 {
1835  BLOCK_START("Being::logic")
1836  if (A_UNLIKELY(mSpeechTime != 0))
1837  {
1838  mSpeechTime--;
1839  if (mSpeechTime == 0 && mText != nullptr)
1840  delete2(mText)
1841  }
1842 
1843  if (A_UNLIKELY(mOwner != nullptr))
1844  {
1845  if (mType == ActorType::Homunculus ||
1847  {
1848  botLogic();
1849  }
1850  }
1851 
1852  const int time = tick_time * MILLISECONDS_IN_A_TICK;
1853  if (mEmotionSprite != nullptr)
1854  mEmotionSprite->update(time);
1856  (*it)->update(time);
1858  (*it)->update(time);
1859 
1861  {
1862  mCastEndTime = 0;
1864  }
1865 
1867  {
1868  mAnimationEffect->update(time);
1871  }
1873  {
1874  mCastingEffect->update(time);
1877  }
1878  for_each_badges()
1879  {
1880  AnimatedSprite *restrict const sprite = mBadges[f];
1881  if (sprite != nullptr)
1882  sprite->update(time);
1883  }
1884 
1885  int frameCount = CAST_S32(getFrameCount());
1886 
1887  switch (mAction)
1888  {
1889  case BeingAction::STAND:
1890  case BeingAction::SIT:
1891  case BeingAction::DEAD:
1892  case BeingAction::HURT:
1893  case BeingAction::SPAWN:
1894  case BeingAction::CAST:
1895  default:
1896  break;
1897 
1898  case BeingAction::MOVE:
1899  {
1901  nextTile();
1902  break;
1903  }
1904 
1905  case BeingAction::ATTACK:
1906  {
1907  if (mActionTime == 0)
1908  break;
1909 
1910  int curFrame = 0;
1911  if (mAttackSpeed != 0)
1912  {
1913  curFrame = (get_elapsed_time(mActionTime) * frameCount)
1914  / mAttackSpeed;
1915  }
1916 
1917  if (this == localPlayer && curFrame >= frameCount)
1918  nextTile();
1919 
1920  break;
1921  }
1922 
1923  case BeingAction::PRESTAND:
1924  {
1925  if (get_elapsed_time1(mPreStandTime) > 10)
1927  break;
1928  }
1929  }
1930 
1932  {
1933  const int xOffset = getOffset<BeingDirection::LEFT,
1935  const int yOffset = getOffset<BeingDirection::UP,
1937  int offset = xOffset;
1938  if (offset == 0)
1939  offset = yOffset;
1940 
1941  if ((xOffset == 0) && (yOffset == 0))
1942  mNeedPosUpdate = false;
1943 
1944  const int halfTile = mapTileSize / 2;
1945  const float offset2 = static_cast<float>(
1946  mPixelOffsetY * abs(offset)) / 2;
1947 // mSortOffsetY = (mOldHeight - mFixedOffsetY + mPixelOffsetY)
1948 // * halfTile - offset2;
1949  mSortOffsetY = 0;
1950  const float yOffset3 = (mY + 1) * mapTileSize + yOffset
1951  - (mOldHeight + mPixelOffsetY) * halfTile + offset2;
1952 
1953  // Update pixel coordinates
1954  setPixelPositionF(static_cast<float>(mX * mapTileSize
1955  + mapTileSize / 2 + xOffset),
1956  yOffset3,
1957  0.0F);
1958  }
1959 
1961  {
1962  mEmotionTime--;
1963  if (mEmotionTime == 0)
1965  }
1966 
1968 
1969  if (frameCount < 10)
1970  frameCount = 10;
1971 
1972  if (A_UNLIKELY(!isAlive() &&
1973  mSpeed != 0 &&
1975  get_elapsed_time(mActionTime) / mSpeed >= frameCount))
1976  {
1977  if (mType != ActorType::Player && (actorManager != nullptr))
1978  actorManager->destroy(this);
1979  }
1980 
1981  const SoundInfo *restrict const sound = mNextSound.sound;
1982  if (A_UNLIKELY(sound))
1983  {
1984  const int time2 = tick_time;
1985  if (time2 > mNextSound.time)
1986  {
1987  soundManager.playSfx(sound->sound,
1988  mNextSound.x,
1989  mNextSound.y);
1990  mNextSound.sound = nullptr;
1991  mNextSound.time = time2 + sound->delay;
1992  }
1993  }
1994 
1995  BLOCK_END("Being::logic")
1996 }
1997 
1999 {
2000  if ((mOwner == nullptr) || (mMap == nullptr) || (mInfo == nullptr))
2001  return;
2002 
2003  const int time = tick_time;
2004  const int thinkTime = mInfo->getThinkTime();
2005  if (abs(CAST_S32(mMoveTime) - time) < thinkTime)
2006  return;
2007 
2008  mMoveTime = time;
2009 
2010  int dstX = mOwner->mX;
2011  int dstY = mOwner->mY;
2012  const int warpDist = mInfo->getWarpDist();
2013  const int divX = abs(dstX - mX);
2014  const int divY = abs(dstY - mY);
2015 
2016  if (divX >= warpDist || divY >= warpDist)
2017  {
2020  else
2022  mBotAi = true;
2023  return;
2024  }
2025  if (!mBotAi)
2026  return;
2027  if (mAction == BeingAction::MOVE)
2028  {
2030  {
2031  updateBotFollow(dstX, dstY,
2032  divX, divY);
2033  }
2034  return;
2035  }
2036 
2037  switch (mOwner->mAction)
2038  {
2039  case BeingAction::MOVE:
2040  case BeingAction::PRESTAND:
2041  updateBotFollow(dstX, dstY,
2042  divX, divY);
2043  break;
2044  case BeingAction::STAND:
2045  case BeingAction::SPAWN:
2046  botFixOffset(dstX, dstY);
2047  moveBotTo(dstX, dstY);
2048  break;
2049  case BeingAction::ATTACK:
2050  {
2051  const Being *const target = localPlayer->getTarget();
2052  if (target == nullptr)
2053  return;
2054  const BeingId targetId = target->getId();
2056  {
2057  homunculusHandler->attack(targetId,
2058  Keep_true);
2059  }
2060  else
2061  {
2062  mercenaryHandler->attack(targetId,
2063  Keep_true);
2064  }
2065  break;
2066  }
2067  case BeingAction::SIT:
2068  botFixOffset(dstX, dstY);
2069  moveBotTo(dstX, dstY);
2070  break;
2071  case BeingAction::DEAD:
2072  botFixOffset(dstX, dstY);
2073  moveBotTo(dstX, dstY);
2074  break;
2075  case BeingAction::CAST:
2076  case BeingAction::HURT:
2077  default:
2078  break;
2079  }
2080 }
2081 
2083  int &restrict dstY) const
2084 {
2085  if ((mInfo == nullptr) || (mOwner == nullptr))
2086  return;
2087 
2088  int offsetX1;
2089  int offsetY1;
2090  switch (mOwner->getCurrentAction())
2091  {
2092  case BeingAction::SIT:
2093  offsetX1 = mInfo->getSitOffsetX();
2094  offsetY1 = mInfo->getSitOffsetY();
2095  break;
2096 
2097  case BeingAction::MOVE:
2098  offsetX1 = mInfo->getMoveOffsetX();
2099  offsetY1 = mInfo->getMoveOffsetY();
2100  break;
2101 
2102  case BeingAction::DEAD:
2103  offsetX1 = mInfo->getDeadOffsetX();
2104  offsetY1 = mInfo->getDeadOffsetY();
2105  break;
2106 
2107  case BeingAction::ATTACK:
2108  offsetX1 = mInfo->getAttackOffsetX();
2109  offsetY1 = mInfo->getAttackOffsetY();
2110  break;
2111 
2112  case BeingAction::SPAWN:
2113  case BeingAction::HURT:
2114  case BeingAction::STAND:
2115  case BeingAction::PRESTAND:
2116  case BeingAction::CAST:
2117  default:
2118  offsetX1 = mInfo->getTargetOffsetX();
2119  offsetY1 = mInfo->getTargetOffsetY();
2120  break;
2121  }
2122 
2123  int offsetX = offsetX1;
2124  int offsetY = offsetY1;
2125  switch (mOwner->mDirection)
2126  {
2127  case BeingDirection::LEFT:
2128  offsetX = -offsetY1;
2129  offsetY = offsetX1;
2130  break;
2131  case BeingDirection::RIGHT:
2132  offsetX = offsetY1;
2133  offsetY = -offsetX1;
2134  break;
2135  case BeingDirection::UP:
2136  offsetY = -offsetY;
2137  offsetX = -offsetX;
2138  break;
2139  default:
2140  case BeingDirection::DOWN:
2141  break;
2142  }
2143  dstX += offsetX;
2144  dstY += offsetY;
2145  if (mMap != nullptr)
2146  {
2147  if (!mMap->getWalk(dstX, dstY, getBlockWalkMask()))
2148  {
2149  dstX = mOwner->mX;
2150  dstY = mOwner->mY;
2151  }
2152  }
2153 }
2154 
2156  int dstY,
2157  const int divX,
2158  const int divY)
2159 {
2160  const int followDist = mInfo->getStartFollowDist();
2161  const int dist = mInfo->getFollowDist();
2162  if (divX > followDist || divY > followDist)
2163  {
2164  if (dist > 0)
2165  {
2166  if (divX > followDist)
2167  {
2168  if (dstX > mX + dist)
2169  dstX -= dist;
2170  else if (dstX + dist <= mX)
2171  dstX += dist;
2172  }
2173  else
2174  {
2175  dstX = mX;
2176  }
2177  if (divY > followDist)
2178  {
2179  if (dstY > mY + dist)
2180  dstY -= dist;
2181  else if (dstX + dist <= mX)
2182  dstY += dist;
2183  }
2184  else
2185  {
2186  dstY = mY;
2187  }
2188  }
2189  botFixOffset(dstX, dstY);
2190  moveBotTo(dstX, dstY);
2191  }
2192 }
2193 
2194 void Being::moveBotTo(int dstX,
2195  int dstY)
2196 {
2197  const int dstX0 = mOwner->mX;
2198  const int dstY0 = mOwner->mY;
2199  const unsigned char blockWalkMask = getBlockWalkMask();
2200  if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2201  {
2202  if (dstX != dstX0)
2203  {
2204  dstX = dstX0;
2205  if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2206  dstY = dstY0;
2207  }
2208  else if (dstY != dstY0)
2209  {
2210  dstY = dstY0;
2211  if (!mMap->getWalk(dstX, dstY, blockWalkMask))
2212  dstX = dstX0;
2213  }
2214  }
2215  if (mX != dstX || mY != dstY)
2216  {
2218  homunculusHandler->move(dstX, dstY);
2219  else
2220  mercenaryHandler->move(dstX, dstY);
2221  return;
2222  }
2223  updateBotDirection(dstX, dstY);
2224 }
2225 
2226 void Being::updateBotDirection(const int dstX,
2227  const int dstY)
2228 {
2229  int directionType = 0;
2230  switch (mOwner->getCurrentAction())
2231  {
2232  case BeingAction::STAND:
2233  case BeingAction::MOVE:
2234  case BeingAction::HURT:
2235  case BeingAction::SPAWN:
2236  case BeingAction::CAST:
2237  case BeingAction::PRESTAND:
2238  default:
2239  directionType = mInfo->getDirectionType();
2240  break;
2241  case BeingAction::SIT:
2242  directionType = mInfo->getSitDirectionType();
2243  break;
2244  case BeingAction::DEAD:
2245  directionType = mInfo->getDeadDirectionType();
2246  break;
2247  case BeingAction::ATTACK:
2248  directionType = mInfo->getAttackDirectionType();
2249  break;
2250  }
2251 
2252  uint8_t newDir = 0;
2253  switch (directionType)
2254  {
2255  case 0:
2256  default:
2257  return;
2258 
2259  case 1:
2260  newDir = mOwner->mDirection;
2261  break;
2262 
2263  case 2:
2264  {
2265  const int dstX0 = mOwner->mX;
2266  const int dstY0 = mOwner->mY;
2267  if (dstX > dstX0)
2268  newDir |= BeingDirection::LEFT;
2269  else if (dstX < dstX0)
2270  newDir |= BeingDirection::RIGHT;
2271  if (dstY > dstY0)
2272  newDir |= BeingDirection::UP;
2273  else if (dstY < dstY0)
2274  newDir |= BeingDirection::DOWN;
2275  break;
2276  }
2277  case 3:
2278  {
2279  const int dstX0 = mOwner->mX;
2280  const int dstY0 = mOwner->mY;
2281  if (dstX > dstX0)
2282  newDir |= BeingDirection::RIGHT;
2283  else if (dstX < dstX0)
2284  newDir |= BeingDirection::LEFT;
2285  if (dstY > dstY0)
2286  newDir |= BeingDirection::DOWN;
2287  else if (dstY < dstY0)
2288  newDir |= BeingDirection::UP;
2289  break;
2290  }
2291  case 4:
2292  {
2293  const int dstX2 = mOwner->getLastAttackX();
2294  const int dstY2 = mOwner->getLastAttackY();
2295  if (dstX > dstX2)
2296  newDir |= BeingDirection::LEFT;
2297  else if (dstX < dstX2)
2298  newDir |= BeingDirection::RIGHT;
2299  if (dstY > dstY2)
2300  newDir |= BeingDirection::UP;
2301  else if (dstY < dstY2)
2302  newDir |= BeingDirection::DOWN;
2303  break;
2304  }
2305  }
2306  if ((newDir != 0u) && newDir != mDirection)
2307  {
2310  else
2311  mercenaryHandler->setDirection(newDir);
2312  }
2313 }
2314 
2316 {
2317  const int px = mPixelX - mapTileSize / 2;
2318  const int py = mPixelY - mapTileSize * 2 - mapTileSize;
2320  mBadgesCount != 0u)
2321  {
2322  if (mDispName != nullptr &&
2323  gui != nullptr)
2324  {
2326  {
2327  const Font *restrict const font = gui->getFont();
2329  mBadgesY = mDispName->getY() - font->getHeight();
2330  }
2331  else if (mShowBadges == BadgeDrawType::Bottom)
2332  {
2333  mBadgesX = px + 8 - mBadgesCount * 8;
2335  {
2336  mBadgesY = mDispName->getY();
2337  }
2338  else
2339  {
2340  mBadgesY = py + settings.playerNameOffset + 16;
2341  }
2342  }
2343  else
2344  {
2345  mBadgesX = px + 8 - mBadgesCount * 8;
2347  mBadgesY = py - mDispName->getHeight();
2348  else
2349  mBadgesY = py;
2350  }
2351  }
2352  else
2353  {
2355  {
2357  mBadgesY = py;
2358  }
2359  else if (mShowBadges == BadgeDrawType::Bottom)
2360  {
2361  mBadgesX = px + 8 - mBadgesCount * 8;
2362  const int height = settings.playerNameOffset;
2364  mBadgesY = py + height;
2365  else
2366  mBadgesY = py + height + 16;
2367  }
2368  else
2369  {
2370  mBadgesX = px + 8 - mBadgesCount * 8;
2371  mBadgesY = py;
2372  }
2373  }
2374  }
2375 }
2376 
2377 void Being::drawEmotion(Graphics *restrict const graphics,
2378  const int offsetX,
2379  const int offsetY) const restrict2
2380 {
2381  if (mErased)
2382  return;
2383 
2384  const int px = mPixelX - offsetX - mapTileSize / 2;
2385  const int py = mPixelY - offsetY - mapTileSize * 2 - mapTileSize;
2386  if (mAnimationEffect != nullptr)
2387  mAnimationEffect->draw(graphics, px, py);
2389  mBadgesCount != 0u)
2390  {
2391  int x = mBadgesX - offsetX;
2392  const int y = mBadgesY - offsetY;
2393  for_each_badges()
2394  {
2395  const AnimatedSprite *restrict const sprite = mBadges[f];
2396  if (sprite != nullptr)
2397  {
2398  sprite->draw(graphics, x, y);
2399  x += 16;
2400  }
2401  }
2402  }
2403  if (mEmotionSprite != nullptr)
2404  mEmotionSprite->draw(graphics, px, py);
2405 }
2406 
2407 void Being::drawSpeech(const int offsetX,
2408  const int offsetY) restrict2
2409 {
2410  if (mErased)
2411  return;
2412  if (mSpeech.empty())
2413  return;
2414 
2415  const int px = mPixelX - offsetX;
2416  const int py = mPixelY - offsetY;
2417  const int speech = mSpeechType;
2418 
2419  // Draw speech above this being
2420  if (mSpeechTime == 0)
2421  {
2422  if (mSpeechBubble != nullptr &&
2424  {
2426  }
2427  mSpeech.clear();
2428  }
2429  else if (mSpeechTime > 0 && (speech == BeingSpeech::NAME_IN_BUBBLE ||
2430  speech == BeingSpeech::NO_NAME_IN_BUBBLE))
2431  {
2432  delete2(mText)
2433 
2434  if (mSpeechBubble != nullptr)
2435  {
2437  py - getHeight() - (mSpeechBubble->getHeight()));
2439  }
2440  }
2441  else if (mSpeechTime > 0 && speech == BeingSpeech::TEXT_OVERHEAD)
2442  {
2443  if (mSpeechBubble != nullptr)
2445 
2446  if ((mText == nullptr) && (userPalette != nullptr))
2447  {
2448  mText = new Text(mSpeech,
2449  mPixelX,
2450  mPixelY - getHeight(),
2453  Speech_true,
2454  nullptr);
2456  (mY + 1) * mapTileSize - getHeight() - mText->getHeight() - 9,
2457  mMoveNames);
2458  }
2459  }
2460  else if (speech == BeingSpeech::NO_SPEECH)
2461  {
2462  if (mSpeechBubble != nullptr)
2464  delete2(mText)
2465  }
2466 }
2467 
2468 template<signed char pos, signed char neg>
2470 {
2471  // Check whether we're walking in the requested direction
2472  if (mAction != BeingAction::MOVE || !(mDirection & (pos | neg)))
2473  return 0;
2474 
2475  int offset = 0;
2476 
2477  if (mMap && mSpeed)
2478  {
2479  const int time = get_elapsed_time(mActionTime);
2480  offset = (pos == BeingDirection::LEFT &&
2481  neg == BeingDirection::RIGHT) ?
2482  (time * mMap->getTileWidth() / mSpeed)
2483  : (time * mMap->getTileHeight() / mSpeed);
2484  }
2485 
2486  // We calculate the offset _from_ the _target_ location
2487  offset -= mapTileSize;
2488  if (offset > 0)
2489  offset = 0;
2490 
2491  // Going into negative direction? Invert the offset.
2492  if (mDirection & pos)
2493  offset = -offset;
2494 
2495  if (offset > mapTileSize)
2496  offset = mapTileSize;
2497  if (offset < -mapTileSize)
2498  offset = -mapTileSize;
2499 
2500  return offset;
2501 }
2502 
2504 {
2505  if (mDispName != nullptr)
2506  {
2507  int offsetX = mPixelX;
2508  int offsetY = mPixelY;
2509  if (mInfo != nullptr)
2510  {
2511  offsetX += mInfo->getNameOffsetX();
2512  offsetY += mInfo->getNameOffsetY();
2513  }
2514  // Monster names show above the sprite instead of below it
2515  if (mType == ActorType::Monster ||
2517  {
2518  offsetY += - settings.playerNameOffset - mDispName->getHeight();
2519  }
2520  mDispName->adviseXY(offsetX, offsetY, mMoveNames);
2521  }
2523 }
2524 
2525 void Being::optionChanged(const std::string &restrict value) restrict2
2526 {
2527  if (mType == ActorType::Player && value == "visiblenames")
2528  {
2529  setShowName(config.getIntValue("visiblenames") == VisibleName::Show);
2531  }
2532 }
2533 
2535 {
2536  if (mDispName != nullptr)
2537  mDispName->flash(time);
2538 }
2539 
2541 {
2542  const std::string &restrict str = getGenderSign();
2543  if (str.empty())
2544  return str;
2545  else
2546  return std::string(" ").append(str);
2547 }
2548 
2549 std::string Being::getGenderSign() const restrict2
2550 {
2551  std::string str;
2552  if (mShowGender)
2553  {
2554  if (getGender() == Gender::FEMALE)
2555  str = "\u2640";
2556  else if (getGender() == Gender::MALE)
2557  str = "\u2642";
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 
2583  delete2(mDispName);
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:
2650  255U);
2651  break;
2652  case 2:
2654  255U);
2655  break;
2656  case 3:
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  {
2701  }
2702  else
2703  {
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);
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);
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);
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);
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 
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);
3131  if (beingEquipmentWindow != nullptr)
3133 }
3134 
3135 void Being::setWeaponId(const int id) restrict2
3136 {
3137  if (id == 0)
3138  mEquippedWeapon = nullptr;
3139  else
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 {
3649  if (show &&
3650  mIsGM &&
3653  {
3654  const std::string &gmBadge = GroupDb::getBadge(mGroupId);
3655  if (!gmBadge.empty())
3656  {
3658  paths.getStringValue("badges") + gmBadge,
3659  0);
3660  }
3661  }
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  {
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 
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  {
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 &&
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 {
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 &&
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 {
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 {
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 {
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  {
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();
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  {
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  {
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 
4929 {
4930  if ((effectManager != nullptr) &&
4932  (mSpecialParticle == nullptr) &&
4933  effect != -1)
4934  {
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;
4963  paths.getStringValue("sprites") + name,
4964  0);
4965 }
4966 
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;
5019  mNeedPosUpdate = true;
5020  }
5021 }
5022 
5024 {
5025  mCastEndTime = 0;
5027  ActorSprite::setMap(map);
5028  if (mMap != nullptr)
5029  {
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);
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);
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);
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)
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 {
5200  if (show &&
5201  mTeamId != 0u &&
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  }
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 {
5229  if (show &&
5230  !mPartyName.empty() &&
5232  {
5233  const std::string badge = BadgesDB::getPartyBadge(mPartyName);
5234  if (!badge.empty())
5235  {
5237  paths.getStringValue("badges") + badge,
5238  0);
5239  }
5240  }
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 {
5258  if (show &&
5259  mShop &&
5261  {
5262  const std::string badge = paths.getStringValue("shopbadge");
5263  if (!badge.empty())
5264  {
5266  paths.getStringValue("badges") + badge,
5267  0);
5268  }
5269  }
5272 }
5273 
5275 {
5277  if (show &&
5278  mInactive &&
5280  {
5281  const std::string badge = paths.getStringValue("inactivebadge");
5282  if (!badge.empty())
5283  {
5285  paths.getStringValue("badges") + badge,
5286  0);
5287  }
5288  }
5291 }
5292 
5293 void Being::showAwayBadge(const bool show) restrict2
5294 {
5296  if (show &&
5297  mAway &&
5299  {
5300  const std::string badge = paths.getStringValue("awaybadge");
5301  if (!badge.empty())
5302  {
5304  paths.getStringValue("badges") + badge,
5305  0);
5306  }
5307  }
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();
5334 }
5335 
5336 void Being::setBuyBoard(const std::string &restrict text) restrict2
5337 {
5338  mShop = !text.empty() || !mSellBoard.empty();
5339  mBuyBoard = text;
5340  updateName();
5342 }
5343 
5344 void Being::enableShop(const bool b) restrict2
5345 {
5346  mShop = b;
5347  updateName();
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  {
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);
5446  }
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);
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  {
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();
5518  mSpiritParticles.pop_back();
5519  }
5520 }
5521 
5522 void Being::stopCast(const bool b)
5523 {
5524  if (b &