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