summaryrefslogtreecommitdiff
path: root/src/being/being.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/being/being.cpp')
-rw-r--r--src/being/being.cpp3041
1 files changed, 3041 insertions, 0 deletions
diff --git a/src/being/being.cpp b/src/being/being.cpp
new file mode 100644
index 000000000..7dfe9d9b6
--- /dev/null
+++ b/src/being/being.cpp
@@ -0,0 +1,3041 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/being.h"
+
+#include "actorspritemanager.h"
+#include "animatedsprite.h"
+#include "beingequipbackend.h"
+#include "client.h"
+#include "effectmanager.h"
+#include "guild.h"
+#include "party.h"
+#include "soundconsts.h"
+#include "soundmanager.h"
+#include "text.h"
+
+#include "being/beingcacheentry.h"
+#include "being/playerrelations.h"
+
+#include "particle/particle.h"
+
+#include "gui/equipmentwindow.h"
+#include "gui/socialwindow.h"
+#include "gui/speechbubble.h"
+#include "gui/sdlfont.h"
+#include "gui/skilldialog.h"
+
+#include "net/charserverhandler.h"
+#include "net/gamehandler.h"
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+#include "net/playerhandler.h"
+
+#include "resources/avatardb.h"
+#include "resources/emotedb.h"
+#include "resources/iteminfo.h"
+#include "resources/monsterdb.h"
+#include "resources/npcdb.h"
+#include "resources/petdb.h"
+#include "resources/resourcemanager.h"
+
+#include "gui/widgets/langtab.h"
+#include "gui/widgets/skillinfo.h"
+
+#include "utils/gettext.h"
+
+#include <cmath>
+
+#include "debug.h"
+
+const unsigned int CACHE_SIZE = 50;
+
+int Being::mNumberOfHairstyles = 1;
+int Being::mNumberOfRaces = 1;
+
+int Being::mUpdateConfigTime = 0;
+unsigned int Being::mConfLineLim = 0;
+int Being::mSpeechType = 0;
+bool Being::mHighlightMapPortals = false;
+bool Being::mHighlightMonsterAttackRange = false;
+bool Being::mLowTraffic = true;
+bool Being::mDrawHotKeys = true;
+bool Being::mShowBattleEvents = false;
+bool Being::mShowMobHP = false;
+bool Being::mShowOwnHP = false;
+bool Being::mShowGender = false;
+bool Being::mShowLevel = false;
+bool Being::mShowPlayersStatus = false;
+bool Being::mEnableReorderSprites = true;
+bool Being::mHideErased = false;
+bool Being::mMoveNames = false;
+int Being::mAwayEffect = -1;
+
+std::list<BeingCacheEntry*> beingInfoCache;
+typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
+typedef std::map<int, int>::const_iterator IntMapCIter;
+
+Being::Being(const int id, const Type type, const uint16_t subtype,
+ Map *const map) :
+ ActorSprite(id),
+ mNextSound(),
+ mInfo(BeingInfo::unknown),
+ mEmotionSprite(nullptr),
+ mAnimationEffect(nullptr),
+ mSpriteAction(SpriteAction::STAND),
+ mName(),
+ mRaceName(),
+ mPartyName(),
+ mGuildName(),
+ mSpeech(),
+ mDispName(nullptr),
+ mNameColor(nullptr),
+ mEquippedWeapon(nullptr),
+ mPath(),
+ mText(nullptr),
+ mTextColor(nullptr),
+ mDest(),
+ mSpriteColors(),
+ mSpriteIDs(),
+ mSpriteColorsIds(),
+ mGuilds(),
+ mParty(nullptr),
+ mActionTime(0),
+ mEmotionTime(0),
+ mSpeechTime(0),
+ mAttackSpeed(350),
+ mLevel(0),
+ mAttackRange(1),
+ mGender(GENDER_UNSPECIFIED),
+ mAction(STAND),
+ mSubType(0xFFFF),
+ mDirection(DOWN),
+ mDirectionDelayed(0),
+ mSpriteDirection(DIRECTION_DOWN),
+ mShowName(false),
+ mIsGM(false),
+ mType(type),
+ mSpeechBubble(new SpeechBubble),
+ mWalkSpeed(Net::getPlayerHandler()->getDefaultWalkSpeed()),
+ mIp(),
+ mSpriteRemap(new int[20]),
+ mSpriteHide(new int[20]),
+ mComment(),
+ mPet(nullptr),
+ mOwner(nullptr),
+ mSpecialParticle(nullptr),
+ mX(0),
+ mY(0),
+ mDamageTaken(0),
+ mHP(0),
+ mMaxHP(0),
+ mDistance(0),
+ mIsReachable(REACH_UNKNOWN),
+ mGoodStatus(-1),
+ mMoveTime(0),
+ mAttackTime(0),
+ mTalkTime(0),
+ mOtherTime(0),
+ mTestTime(cur_time),
+ mAttackDelay(0),
+ mMinHit(0),
+ mMaxHit(0),
+ mCriticalHit(0),
+ mPvpRank(0),
+ mNumber(100),
+ mPetId(0),
+ mLook(0),
+ mHairColor(0),
+ mErased(false),
+ mEnemy(false),
+ mGotComment(false),
+ mAdvanced(false),
+ mShop(false),
+ mAway(false),
+ mInactive(false)
+{
+ for (int f = 0; f < 20; f ++)
+ {
+ mSpriteRemap[f] = f;
+ mSpriteHide[f] = 0;
+ }
+
+ setMap(map);
+ setSubtype(subtype, 0);
+
+ if (mType == PLAYER)
+ mShowName = config.getBoolValue("visiblenames");
+ else if (mType != NPC)
+ mGotComment = true;
+
+ config.addListener("visiblenames", this);
+
+ reReadConfig();
+
+ if (mType == NPC)
+ setShowName(true);
+ else
+ setShowName(mShowName);
+
+ updateColors();
+ updatePercentHP();
+}
+
+Being::~Being()
+{
+ config.removeListener("visiblenames", this);
+
+ delete [] mSpriteRemap;
+ mSpriteRemap = nullptr;
+ delete [] mSpriteHide;
+ mSpriteHide = nullptr;
+
+ delete mSpeechBubble;
+ mSpeechBubble = nullptr;
+ delete mDispName;
+ mDispName = nullptr;
+ delete mText;
+ mText = nullptr;
+
+ delete mEmotionSprite;
+ mEmotionSprite = nullptr;
+ delete mAnimationEffect;
+ mAnimationEffect = nullptr;
+
+ if (mOwner)
+ mOwner->setPet(nullptr);
+ if (mPet)
+ mPet->setOwner(nullptr);
+}
+
+void Being::setSubtype(const uint16_t subtype, const uint8_t look)
+{
+ if (!mInfo)
+ return;
+
+ if (subtype == mSubType && mLook == look)
+ return;
+
+ mSubType = subtype;
+ mLook = look;
+
+ if (mType == MONSTER)
+ {
+ mInfo = MonsterDB::get(mSubType);
+ if (mInfo)
+ {
+ setName(mInfo->getName());
+ setupSpriteDisplay(mInfo->getDisplay(), true, 0,
+ mInfo->getColor(mLook));
+ mYDiff = mInfo->getSortOffsetY();
+ }
+ }
+ else if (mType == NPC)
+ {
+ mInfo = NPCDB::get(mSubType);
+ if (mInfo)
+ {
+ setupSpriteDisplay(mInfo->getDisplay(), false);
+ mYDiff = mInfo->getSortOffsetY();
+ }
+ }
+ else if (mType == AVATAR)
+ {
+ mInfo = AvatarDB::get(mSubType);
+ if (mInfo)
+ setupSpriteDisplay(mInfo->getDisplay(), false);
+ }
+ else if (mType == PET)
+ {
+ mInfo = PETDB::get(mId);
+ if (mInfo)
+ {
+ setupSpriteDisplay(mInfo->getDisplay(), false);
+ mYDiff = mInfo->getSortOffsetY();
+ }
+ }
+ else if (mType == PLAYER)
+ {
+ int id = -100 - subtype;
+
+ // Prevent showing errors when sprite doesn't exist
+ if (!ItemDB::exists(id))
+ {
+ id = -100;
+ // TRANSLATORS: default race name
+ setRaceName(_("Human"));
+ if (Net::getCharServerHandler())
+ setSprite(Net::getCharServerHandler()->baseSprite(), id);
+ }
+ else
+ {
+ const ItemInfo &info = ItemDB::get(id);
+ setRaceName(info.getName());
+ if (Net::getCharServerHandler())
+ {
+ setSprite(Net::getCharServerHandler()->baseSprite(),
+ id, info.getColor(mLook));
+ }
+ }
+ }
+}
+
+ActorSprite::TargetCursorSize Being::getTargetCursorSize() const
+{
+ if (!mInfo)
+ return ActorSprite::TC_SMALL;
+
+ return mInfo->getTargetCursorSize();
+}
+
+void Being::setPosition(const Vector &pos)
+{
+ Actor::setPosition(pos);
+
+ updateCoords();
+
+ if (mText)
+ {
+ mText->adviseXY(static_cast<int>(pos.x), static_cast<int>(pos.y)
+ - getHeight() - mText->getHeight() - 6, mMoveNames);
+ }
+}
+
+void Being::setDestination(const int dstX, const int dstY)
+{
+ // We can't calculate anything without a map anyway.
+ if (!mMap)
+ return;
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ setPath(mMap->findPath(mX, mY, dstX, dstY, getWalkMask()));
+ return;
+ }
+
+#ifdef MANASERV_SUPPORT
+ // Don't handle flawed destinations from server...
+ if (dstX == 0 || dstY == 0)
+ return;
+
+ // If the destination is unwalkable, don't bother trying to get there
+ if (!mMap->getWalk(dstX / 32, dstY / 32))
+ return;
+
+ Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
+ dstX, dstY);
+ Path thisPath = mMap->findPixelPath(static_cast<int>(mPos.x),
+ static_cast<int>(mPos.y), dest.x, dest.y,
+ static_cast<int>(getCollisionRadius()),
+ static_cast<unsigned char>(getWalkMask()));
+
+ if (thisPath.empty())
+ {
+ // If there is no path but the destination is on the same walkable tile,
+ // we accept it.
+ if (static_cast<int>(mPos.x) / 32 == dest.x / 32
+ && static_cast<int>(mPos.y) / 32 == dest.y / 32)
+ {
+ mDest.x = static_cast<float>(dest.x);
+ mDest.y = static_cast<float>(dest.y);
+ }
+ setPath(Path());
+ return;
+ }
+
+ // The destination is valid, so we set it.
+ mDest.x = static_cast<float>(dest.x);
+ mDest.y = static_cast<float>(dest.y);
+
+ setPath(thisPath);
+#endif
+}
+
+void Being::clearPath()
+{
+ mPath.clear();
+}
+
+void Being::setPath(const Path &path)
+{
+ mPath = path;
+ if (mPath.empty())
+ return;
+
+#ifdef MANASERV_SUPPORT
+ if ((Net::getNetworkType() != ServerInfo::MANASERV) &&
+ mAction != MOVE && mAction != DEAD)
+#else
+ if (mAction != MOVE && mAction != DEAD)
+#endif
+ {
+ nextTile();
+ mActionTime = tick_time;
+ }
+}
+
+void Being::setSpeech(const std::string &text, const std::string &channel,
+ int time)
+{
+ if (!userPalette)
+ return;
+
+ if (!channel.empty() && (langChatTab && langChatTab->getChannelName()
+ != channel))
+ {
+ return;
+ }
+
+ // Remove colors
+ mSpeech = removeColors(text);
+
+ // Trim whitespace
+ trim(mSpeech);
+
+ const unsigned int lineLim = mConfLineLim;
+ if (lineLim > 0 && mSpeech.length() > lineLim)
+ mSpeech = mSpeech.substr(0, lineLim);
+
+ trim(mSpeech);
+ if (mSpeech.empty())
+ return;
+
+ if (!time)
+ {
+ const size_t sz = mSpeech.size();
+ if (sz < 200)
+ time = static_cast<int>(SPEECH_TIME - 300 + (3 * sz));
+ }
+
+ if (time < static_cast<int>(SPEECH_MIN_TIME))
+ time = static_cast<int>(SPEECH_MIN_TIME);
+
+ // Check for links
+ size_t start = mSpeech.find('[');
+ size_t e = mSpeech.find(']', start);
+
+ while (start != std::string::npos && e != std::string::npos)
+ {
+ // Catch multiple embeds and ignore them so it doesn't crash the client.
+ while ((mSpeech.find('[', start + 1) != std::string::npos) &&
+ (mSpeech.find('[', start + 1) < e))
+ {
+ start = mSpeech.find('[', start + 1);
+ }
+
+ size_t position = mSpeech.find('|');
+ if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
+ {
+ mSpeech.erase(e, 1);
+ mSpeech.erase(start, (position - start) + 1);
+ }
+ position = mSpeech.find('@');
+
+ while (position != std::string::npos)
+ {
+ mSpeech.erase(position, 2);
+ position = mSpeech.find('@');
+ }
+
+ start = mSpeech.find('[', start + 1);
+ e = mSpeech.find(']', start);
+ }
+
+ if (!mSpeech.empty())
+ {
+ mSpeechTime = time <= static_cast<int>(SPEECH_MAX_TIME)
+ ? time : static_cast<int>(SPEECH_MAX_TIME);
+ }
+
+ const int speech = mSpeechType;
+ if (speech == TEXT_OVERHEAD && userPalette)
+ {
+ delete mText;
+
+ mText = new Text(mSpeech,
+ getPixelX(), getPixelY() - getHeight(),
+ gcn::Graphics::CENTER,
+ &userPalette->getColor(UserPalette::PARTICLE),
+ true);
+ }
+}
+
+void Being::takeDamage(Being *const attacker, const int amount,
+ const AttackType type, const int attackId)
+{
+ if (!userPalette || !attacker)
+ return;
+
+ gcn::Font *font = nullptr;
+ // TRANSLATORS: hit or miss message in attacks
+ const std::string damage = amount ? toString(amount) : type == FLEE ?
+ _("dodge") : _("miss");
+ const gcn::Color *color;
+
+ if (gui)
+ font = gui->getInfoParticleFont();
+
+ // Selecting the right color
+ if (type == CRITICAL || type == FLEE)
+ {
+ if (type == CRITICAL)
+ attacker->setCriticalHit(amount);
+
+ if (attacker == player_node)
+ {
+ color = &userPalette->getColor(
+ UserPalette::HIT_LOCAL_PLAYER_CRITICAL);
+ }
+ else
+ {
+ color = &userPalette->getColor(UserPalette::HIT_CRITICAL);
+ }
+ }
+ else if (!amount)
+ {
+ if (attacker == player_node)
+ {
+ // This is intended to be the wrong direction to visually
+ // differentiate between hits and misses
+ color = &userPalette->getColor(UserPalette::HIT_LOCAL_PLAYER_MISS);
+ }
+ else
+ {
+ color = &userPalette->getColor(UserPalette::MISS);
+ }
+ }
+ else if (mType == MONSTER)
+ {
+ if (attacker == player_node)
+ {
+ color = &userPalette->getColor(
+ UserPalette::HIT_LOCAL_PLAYER_MONSTER);
+ }
+ else
+ {
+ color = &userPalette->getColor(
+ UserPalette::HIT_PLAYER_MONSTER);
+ }
+ }
+ else if (mType == PLAYER && attacker != player_node
+ && this == player_node)
+ {
+ // here player was attacked by other player. mark him as enemy.
+ color = &userPalette->getColor(UserPalette::HIT_PLAYER_PLAYER);
+ attacker->setEnemy(true);
+ attacker->updateColors();
+ }
+ else
+ {
+ color = &userPalette->getColor(UserPalette::HIT_MONSTER_PLAYER);
+ }
+
+ if (chatWindow && mShowBattleEvents)
+ {
+ if (this == player_node)
+ {
+ if (attacker->mType == PLAYER || amount)
+ {
+ chatWindow->battleChatLog(strprintf("%s : Hit you -%d",
+ attacker->getName().c_str(), amount), BY_OTHER);
+ }
+ }
+ else if (attacker == player_node && amount)
+ {
+ chatWindow->battleChatLog(strprintf("%s : You hit %s -%d",
+ attacker->getName().c_str(), getName().c_str(), amount),
+ BY_PLAYER);
+ }
+ }
+ if (font && particleEngine)
+ {
+ // Show damage number
+ particleEngine->addTextSplashEffect(damage,
+ getPixelX(), getPixelY() - 16, color, font, true);
+ }
+
+ if (type != SKILL)
+ attacker->updateHit(amount);
+
+ if (amount > 0)
+ {
+ if (player_node && player_node == this)
+ player_node->setLastHitFrom(attacker->getName());
+
+ mDamageTaken += amount;
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_HURT), this, false, mX, mY);
+
+ if (!mInfo->isStaticMaxHP())
+ {
+ if (!mHP && mInfo->getMaxHP() < mDamageTaken)
+ mInfo->setMaxHP(mDamageTaken);
+ }
+ }
+ if (mHP && isAlive())
+ {
+ mHP -= amount;
+ if (mHP < 0)
+ mHP = 0;
+ }
+
+ if (mType == MONSTER)
+ {
+ updatePercentHP();
+ updateName();
+ }
+ else if (mType == PLAYER && socialWindow && getName() != "")
+ {
+ socialWindow->updateAvatar(getName());
+ }
+
+ if (effectManager)
+ {
+ const int hitEffectId = getHitEffect(attacker, type, attackId);
+ if (hitEffectId >= 0)
+ effectManager->trigger(hitEffectId, this);
+ }
+ }
+ else
+ {
+ if (effectManager)
+ {
+ const int hitEffectId = getHitEffect(attacker,
+ MISS, attackId);
+ if (hitEffectId >= 0)
+ effectManager->trigger(hitEffectId, this);
+ }
+ }
+}
+
+int Being::getHitEffect(const Being *const attacker,
+ const AttackType type, const int attackId) const
+{
+ if (!effectManager)
+ return 0;
+
+ // Init the particle effect path based on current
+ // weapon or default.
+ int hitEffectId = 0;
+ if (type != SKILL)
+ {
+ if (attacker)
+ {
+ const ItemInfo *attackerWeapon = attacker->getEquippedWeapon();
+ if (attackerWeapon && attacker->getType() == PLAYER)
+ {
+ if (type == MISS)
+ hitEffectId = attackerWeapon->getMissEffectId();
+ else if (type != CRITICAL)
+ hitEffectId = attackerWeapon->getHitEffectId();
+ else
+ hitEffectId = attackerWeapon->getCriticalHitEffectId();
+ }
+ else if (attacker->getType() == MONSTER)
+ {
+ const BeingInfo *const info = attacker->getInfo();
+ if (info)
+ {
+ const Attack *atk = info->getAttack(attackId);
+ if (atk)
+ {
+ if (type == MISS)
+ hitEffectId = atk->mMissEffectId;
+ else if (type != CRITICAL)
+ hitEffectId = atk->mHitEffectId;
+ else
+ hitEffectId = atk->mCriticalHitEffectId;
+ }
+ else
+ {
+ hitEffectId = getDefaultEffectId(type);
+ }
+ }
+ }
+ else
+ {
+ hitEffectId = getDefaultEffectId(type);
+ }
+ }
+ else
+ {
+ hitEffectId = getDefaultEffectId(type);
+ }
+ }
+ else
+ {
+ // move skills effects to +100000 in effects list
+ hitEffectId = attackId + 100000;
+ }
+ return hitEffectId;
+}
+
+int Being::getDefaultEffectId(const int type)
+{
+ if (type == MISS)
+ return paths.getIntValue("missEffectId");
+ else if (type != CRITICAL)
+ return paths.getIntValue("hitEffectId");
+ else
+ return paths.getIntValue("criticalHitEffectId");
+}
+
+void Being::handleAttack(Being *const victim, const int damage,
+ const int attackId)
+{
+ if (!victim || !mInfo)
+ return;
+
+ if (this != player_node)
+ setAction(Being::ATTACK, attackId);
+
+ if (mType == PLAYER && mEquippedWeapon)
+ fireMissile(victim, mEquippedWeapon->getMissileParticleFile());
+ else if (mInfo->getAttack(attackId))
+ fireMissile(victim, mInfo->getAttack(attackId)->mMissileParticle);
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ reset();
+ mActionTime = tick_time;
+ }
+
+ if (this != player_node)
+ {
+ const uint8_t dir = calcDirection(victim->getTileX(),
+ victim->getTileY());
+ if (dir)
+ setDirection(dir);
+ }
+ if (damage && victim->mType == PLAYER && victim->mAction == SIT)
+ victim->setAction(STAND);
+
+ if (mType == PLAYER)
+ {
+ if (mSpriteIDs.size() >= 10)
+ {
+ // here 10 is weapon slot
+ int weaponId = mSpriteIDs[10];
+ if (!weaponId)
+ weaponId = -100 - mSubType;
+ const ItemInfo &info = ItemDB::get(weaponId);
+ playSfx(info.getSound((damage > 0) ?
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
+ }
+ }
+ else
+ {
+ playSfx(mInfo->getSound((damage > 0) ?
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
+ }
+}
+
+void Being::handleSkill(Being *const victim, const int damage,
+ const int skillId, const int skillLevel)
+{
+ if (!victim || !mInfo || !skillDialog)
+ return;
+
+ if (this != player_node)
+ setAction(Being::ATTACK, 1);
+
+ SkillInfo *const skill = skillDialog->getSkill(skillId);
+ const SkillData *const data = skill
+ ? skill->getData1(skillLevel) : nullptr;
+ if (data)
+ fireMissile(victim, data->particle);
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ reset();
+ mActionTime = tick_time;
+ }
+
+ if (this != player_node)
+ {
+ const uint8_t dir = calcDirection(victim->getTileX(),
+ victim->getTileY());
+ if (dir)
+ setDirection(dir);
+ }
+ if (damage && victim->mType == PLAYER && victim->mAction == SIT)
+ victim->setAction(STAND);
+ if (data)
+ {
+ if (damage > 0)
+ playSfx(data->soundHit, victim, true, mX, mY);
+ else
+ playSfx(data->soundMiss, victim, true, mX, mY);
+ }
+ else
+ {
+ playSfx(mInfo->getSound((damage > 0) ?
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
+ }
+}
+
+void Being::setName(const std::string &name)
+{
+ if (mType == NPC)
+ {
+ mName = name.substr(0, name.find('#', 0));
+ showName();
+ }
+ else
+ {
+ mName = name;
+
+ if (mType == PLAYER && getShowName())
+ showName();
+ }
+}
+
+void Being::setShowName(const bool doShowName)
+{
+ if (mShowName == doShowName)
+ return;
+
+ mShowName = doShowName;
+
+ if (doShowName)
+ {
+ showName();
+ }
+ else
+ {
+ delete mDispName;
+ mDispName = nullptr;
+ }
+}
+
+void Being::setGuildName(const std::string &name)
+{
+ mGuildName = name;
+}
+
+void Being::setGuildPos(const std::string &pos A_UNUSED)
+{
+}
+
+void Being::addGuild(Guild *const guild)
+{
+ if (!guild)
+ return;
+
+ mGuilds[guild->getId()] = guild;
+
+ if (this == player_node && socialWindow)
+ socialWindow->addTab(guild);
+}
+
+void Being::removeGuild(const int id)
+{
+ if (this == player_node && socialWindow)
+ socialWindow->removeTab(mGuilds[id]);
+
+ if (mGuilds[id])
+ mGuilds[id]->removeMember(getName());
+ mGuilds.erase(id);
+}
+
+Guild *Being::getGuild(const std::string &guildName) const
+{
+ FOR_EACH (GuildsMapCIter, itr, mGuilds)
+ {
+ Guild *const guild = itr->second;
+ if (guild && guild->getName() == guildName)
+ return guild;
+ }
+
+ return nullptr;
+}
+
+Guild *Being::getGuild(const int id) const
+{
+ const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
+ if (itr != mGuilds.end())
+ return itr->second;
+
+ return nullptr;
+}
+
+Guild *Being::getGuild() const
+{
+ const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
+ if (itr != mGuilds.end())
+ return itr->second;
+
+ return nullptr;
+}
+
+void Being::clearGuilds()
+{
+ FOR_EACH (GuildsMapCIter, itr, mGuilds)
+ {
+ Guild *const guild = itr->second;
+
+ if (guild)
+ {
+ if (this == player_node && socialWindow)
+ socialWindow->removeTab(guild);
+
+ guild->removeMember(mId);
+ }
+ }
+
+ mGuilds.clear();
+}
+
+void Being::setParty(Party *const party)
+{
+ if (party == mParty)
+ return;
+
+ Party *const old = mParty;
+ mParty = party;
+
+ if (old)
+ old->removeMember(mId);
+
+ if (party)
+ party->addMember(mId, mName);
+
+ updateColors();
+
+ if (this == player_node && socialWindow)
+ {
+ if (old)
+ socialWindow->removeTab(old);
+
+ if (party)
+ socialWindow->addTab(party);
+ }
+}
+
+void Being::updateGuild()
+{
+ if (!player_node)
+ return;
+
+ Guild *const guild = player_node->getGuild();
+ if (!guild)
+ {
+ clearGuilds();
+ updateColors();
+ return;
+ }
+ if (guild->getMember(getName()))
+ {
+ setGuild(guild);
+ if (!guild->getName().empty())
+ mGuildName = guild->getName();
+ }
+ updateColors();
+}
+
+void Being::setGuild(Guild *const guild)
+{
+ Guild *const old = getGuild();
+ if (guild == old)
+ return;
+
+ clearGuilds();
+ addGuild(guild);
+
+ if (old)
+ old->removeMember(mName);
+
+ updateColors();
+
+ if (this == player_node && socialWindow)
+ {
+ if (old)
+ socialWindow->removeTab(old);
+
+ if (guild)
+ socialWindow->addTab(guild);
+ }
+}
+
+void Being::fireMissile(Being *const victim, const std::string &particle) const
+{
+ if (!victim || particle.empty() || !particleEngine)
+ return;
+
+ Particle *const target = particleEngine->createChild();
+
+ if (!target)
+ return;
+
+ Particle *const missile = target->addEffect(
+ particle, getPixelX(), getPixelY());
+
+ if (missile)
+ {
+ target->moveBy(Vector(0.0f, 0.0f, 32.0f));
+ target->setLifetime(1000);
+ victim->controlParticle(target);
+
+ missile->setDestination(target, 7, 0);
+ missile->setDieDistance(8);
+ missile->setLifetime(900);
+ }
+}
+
+std::string Being::getSitAction() const
+{
+ if (serverVersion < 0)
+ {
+ return SpriteAction::SIT;
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_GROUNDTOP)
+ return SpriteAction::SITTOP;
+ else if (mask & Map::BLOCKMASK_AIR)
+ return SpriteAction::SITSKY;
+ else if (mask & Map::BLOCKMASK_WATER)
+ return SpriteAction::SITWATER;
+ }
+ return SpriteAction::SIT;
+ }
+}
+
+
+std::string Being::getMoveAction() const
+{
+ if (serverVersion < 0)
+ {
+ return SpriteAction::MOVE;
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_AIR)
+ return SpriteAction::FLY;
+ else if (mask & Map::BLOCKMASK_WATER)
+ return SpriteAction::SWIM;
+ }
+ return SpriteAction::MOVE;
+ }
+}
+
+std::string Being::getWeaponAttackAction(const ItemInfo *const weapon) const
+{
+ if (!weapon)
+ return SpriteAction::ATTACK;
+
+ if (serverVersion < 0 || !weapon)
+ {
+ return weapon->getAttackAction();
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_AIR)
+ return weapon->getSkyAttackAction();
+ else if (mask & Map::BLOCKMASK_WATER)
+ return weapon->getWaterAttackAction();
+ }
+ return weapon->getAttackAction();
+ }
+}
+
+std::string Being::getAttackAction(const Attack *const attack1) const
+{
+ if (!attack1)
+ return SpriteAction::ATTACK;
+
+ if (serverVersion < 0 || !attack1)
+ {
+ return attack1->mAction;
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_AIR)
+ return attack1->mSkyAction;
+ else if (mask & Map::BLOCKMASK_WATER)
+ return attack1->mWaterAction;
+ }
+ return attack1->mAction;
+ }
+}
+
+#define getSpriteAction(func, action) \
+ std::string Being::get##func##Action() const \
+{ \
+ if (serverVersion < 0) \
+ { \
+ return SpriteAction::action; \
+ } \
+ else \
+ { \
+ if (mMap) \
+ { \
+ const unsigned char mask = mMap->getBlockMask(mX, mY); \
+ if (mask & Map::BLOCKMASK_AIR) \
+ return SpriteAction::action##SKY; \
+ else if (mask & Map::BLOCKMASK_WATER) \
+ return SpriteAction::action##WATER; \
+ } \
+ return SpriteAction::action; \
+ } \
+}
+
+getSpriteAction(Dead, DEAD)
+getSpriteAction(Stand, STAND)
+getSpriteAction(Spawn, SPAWN)
+
+void Being::setAction(const Action &action, const int attackId)
+{
+ std::string currentAction = SpriteAction::INVALID;
+
+ switch (action)
+ {
+ case MOVE:
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(
+ SOUND_EVENT_MOVE), nullptr, true, mX, mY);
+ }
+ currentAction = getMoveAction();
+ // Note: When adding a run action,
+ // Differentiate walk and run with action name,
+ // while using only the ACTION_MOVE.
+ break;
+ case SIT:
+ currentAction = getSitAction();
+ if (mInfo)
+ {
+ SoundEvent event;
+ if (currentAction == SpriteAction::SITTOP)
+ event = SOUND_EVENT_SITTOP;
+ else
+ event = SOUND_EVENT_SIT;
+ playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
+ }
+ break;
+ case ATTACK:
+ if (mEquippedWeapon)
+ {
+ currentAction = getWeaponAttackAction(mEquippedWeapon);
+ reset();
+ }
+ else
+ {
+ if (!mInfo || !mInfo->getAttack(attackId))
+ break;
+
+ currentAction = getAttackAction(mInfo->getAttack(attackId));
+ reset();
+
+ // attack particle effect
+ if (Particle::enabled)
+ {
+ const int effectId = mInfo->getAttack(attackId)->mEffectId;
+
+ int rotation;
+ switch (mSpriteDirection)
+ {
+ case DIRECTION_DOWN:
+ default:
+ rotation = 0;
+ break;
+ case DIRECTION_LEFT:
+ rotation = 90;
+ break;
+ case DIRECTION_UP:
+ rotation = 180;
+ break;
+ case DIRECTION_RIGHT:
+ rotation = 270;
+ break;
+ }
+ if (Particle::enabled && effectManager && effectId >= 0)
+ effectManager->trigger(effectId, this, rotation);
+ }
+ }
+ break;
+ case HURT:
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_HURT),
+ this, false, mX, mY);
+ }
+ break;
+ case DEAD:
+ currentAction = getDeadAction();
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_DIE), this, true, mX, mY);
+ if (mType == MONSTER || mType == NPC)
+ mYDiff = mInfo->getDeadSortOffsetY();
+ }
+ break;
+ case STAND:
+ currentAction = getStandAction();
+ break;
+ case SPAWN:
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_SPAWN),
+ nullptr, true, mX, mY);
+ }
+ currentAction = getSpawnAction();
+ break;
+ default:
+ logger->log("Being::setAction unknown action: "
+ + toString(static_cast<unsigned>(action)));
+ break;
+ }
+
+ if (currentAction != SpriteAction::INVALID)
+ {
+ mSpriteAction = currentAction;
+ play(currentAction);
+ if (mEmotionSprite)
+ mEmotionSprite->play(currentAction);
+ if (mAnimationEffect)
+ mAnimationEffect->play(currentAction);
+ mAction = action;
+ }
+
+ if (currentAction != SpriteAction::MOVE
+ && currentAction != SpriteAction::FLY
+ && currentAction != SpriteAction::SWIM)
+ {
+ mActionTime = tick_time;
+ }
+}
+
+void Being::setDirection(const uint8_t direction)
+{
+ if (mDirection == direction)
+ return;
+
+ mDirection = direction;
+
+ mDirectionDelayed = 0;
+
+ // if the direction does not change much, keep the common component
+ int mFaceDirection = mDirection & direction;
+ if (!mFaceDirection)
+ mFaceDirection = direction;
+
+ SpriteDirection dir;
+ if (mFaceDirection & UP)
+ {
+ if (mFaceDirection & LEFT)
+ dir = DIRECTION_UPLEFT;
+ else if (mFaceDirection & RIGHT)
+ dir = DIRECTION_UPRIGHT;
+ else
+ dir = DIRECTION_UP;
+ }
+ else if (mFaceDirection & DOWN)
+ {
+ if (mFaceDirection & LEFT)
+ dir = DIRECTION_DOWNLEFT;
+ else if (mFaceDirection & RIGHT)
+ dir = DIRECTION_DOWNRIGHT;
+ else
+ dir = DIRECTION_DOWN;
+ }
+ else if (mFaceDirection & RIGHT)
+ {
+ dir = DIRECTION_RIGHT;
+ }
+ else
+ {
+ dir = DIRECTION_LEFT;
+ }
+ mSpriteDirection = static_cast<uint8_t>(dir);
+
+ CompoundSprite::setSpriteDirection(dir);
+ if (mEmotionSprite)
+ mEmotionSprite->setSpriteDirection(dir);
+ if (mAnimationEffect)
+ mAnimationEffect->setSpriteDirection(dir);
+ recalcSpritesOrder();
+}
+
+uint8_t Being::calcDirection() const
+{
+ uint8_t dir = 0;
+ if (mDest.x > mX)
+ dir |= RIGHT;
+ else if (mDest.x < mX)
+ dir |= LEFT;
+ if (mDest.y > mY)
+ dir |= DOWN;
+ else if (mDest.y < mY)
+ dir |= UP;
+ return dir;
+}
+
+uint8_t Being::calcDirection(const int dstX, const int dstY) const
+{
+ uint8_t dir = 0;
+ if (dstX > mX)
+ dir |= RIGHT;
+ else if (dstX < mX)
+ dir |= LEFT;
+ if (dstY > mY)
+ dir |= DOWN;
+ else if (dstY < mY)
+ dir |= UP;
+ return dir;
+}
+
+void Being::nextTile()
+{
+ if (mPath.empty())
+ {
+ setAction(STAND);
+ return;
+ }
+
+ const Position pos = mPath.front();
+ mPath.pop_front();
+
+ const uint8_t dir = calcDirection(pos.x, pos.y);
+ if (dir)
+ setDirection(dir);
+
+ if (!mMap || !mMap->getWalk(pos.x, pos.y, getWalkMask()))
+ {
+ setAction(STAND);
+ return;
+ }
+
+ mX = pos.x;
+ mY = pos.y;
+ setAction(MOVE);
+ mActionTime += static_cast<int>(mWalkSpeed.x / 10);
+}
+
+void Being::logic()
+{
+ BLOCK_START("Being::logic")
+ // Reduce the time that speech is still displayed
+ if (mSpeechTime > 0)
+ mSpeechTime--;
+
+ // Remove text and speechbubbles if speech boxes aren't being used
+ if (mSpeechTime == 0 && mText)
+ {
+ delete mText;
+ mText = nullptr;
+ }
+
+ const int time = tick_time * MILLISECONDS_IN_A_TICK;
+ if (mEmotionSprite)
+ mEmotionSprite->update(time);
+
+ if (mAnimationEffect)
+ {
+ mAnimationEffect->update(time);
+ if (mAnimationEffect->isTerminated())
+ {
+ delete mAnimationEffect;
+ mAnimationEffect = nullptr;
+ }
+ }
+
+ int frameCount = static_cast<int>(getFrameCount());
+#ifdef MANASERV_SUPPORT
+ if ((Net::getNetworkType() == ServerInfo::MANASERV) && (mAction != DEAD))
+ {
+ const Vector dest = (mPath.empty()) ?
+ mDest : Vector(static_cast<float>(mPath.front().x),
+ static_cast<float>(mPath.front().y));
+
+ // This is a hack that stops NPCs from running off the map...
+ if (mDest.x <= 0 && mDest.y <= 0)
+ {
+ BLOCK_END("Being::logic")
+ return;
+ }
+
+ // The Vector representing the difference between current position
+ // and the next destination path node.
+ Vector dir = dest - mPos;
+
+ const float nominalLength = dir.length();
+
+ // When we've not reached our destination, move to it.
+ if (nominalLength > 0.0f && !mWalkSpeed.isNull())
+ {
+ // The deplacement of a point along a vector is calculated
+ // using the Unit Vector (â) multiplied by the point speed.
+ // â = a / ||a|| (||a|| is the a length.)
+ // Then, diff = (dir/||dir||) * speed.
+ const Vector normalizedDir = dir.normalized();
+ Vector diff(normalizedDir.x * mWalkSpeed.x,
+ normalizedDir.y * mWalkSpeed.y);
+
+ // Test if we don't miss the destination by a move too far:
+ if (diff.length() > nominalLength)
+ {
+ setPosition(mPos + dir);
+
+ // Also, if the destination is reached, try to get the next
+ // path point, if existing.
+ if (!mPath.empty())
+ mPath.pop_front();
+ }
+ // Otherwise, go to it using the nominal speed.
+ else
+ {
+ setPosition(mPos + diff);
+ }
+
+ if (mAction != MOVE)
+ setAction(MOVE);
+
+ // Update the player sprite direction.
+ // N.B.: We only change this if the distance is more than one pixel.
+ if (nominalLength > 1.0f)
+ {
+ int direction = 0;
+ const float dx = std::abs(dir.x);
+ float dy = std::abs(dir.y);
+
+ // When not using mouse for the player, we slightly prefer
+ // UP and DOWN position, especially when walking diagonally.
+ if (player_node && this == player_node &&
+ !player_node->isPathSetByMouse())
+ {
+ dy = dy + 2;
+ }
+
+ if (dx > dy)
+ direction |= (dir.x > 0) ? RIGHT : LEFT;
+ else
+ direction |= (dir.y > 0) ? DOWN : UP;
+
+ setDirection(static_cast<uint8_t>(direction));
+ }
+ }
+ else if (!mPath.empty())
+ {
+ // If the current path node has been reached,
+ // remove it and go to the next one.
+ mPath.pop_front();
+ }
+ else if (mAction == MOVE)
+ {
+ setAction(STAND);
+ }
+ }
+ else
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ switch (mAction)
+ {
+ case STAND:
+ case SIT:
+ case DEAD:
+ case HURT:
+ case SPAWN:
+ default:
+ break;
+
+ case MOVE:
+ {
+ if (getWalkSpeed().x && static_cast<int> ((static_cast<float>(
+ get_elapsed_time(mActionTime)) * static_cast<float>(
+ frameCount)) / getWalkSpeed().x)
+ >= frameCount)
+ {
+ nextTile();
+ }
+ break;
+ }
+
+ case ATTACK:
+ {
+// std::string particleEffect("");
+
+ if (!mActionTime)
+ break;
+
+ int curFrame = 0;
+ if (mAttackSpeed)
+ {
+ curFrame = (get_elapsed_time(mActionTime) * frameCount)
+ / mAttackSpeed;
+ }
+
+ if (this == player_node && curFrame >= frameCount)
+ nextTile();
+
+ break;
+ }
+ }
+
+ // Update pixel coordinates
+ setPosition(static_cast<float>(mX * 32 + 16 + getXOffset()),
+ static_cast<float>(mY * 32 + 32 + getYOffset()));
+ }
+
+ if (mEmotionSprite)
+ {
+ mEmotionTime--;
+ if (mEmotionTime == 0)
+ {
+ delete mEmotionSprite;
+ mEmotionSprite = nullptr;
+ }
+ }
+
+ ActorSprite::logic();
+
+ if (frameCount < 10)
+ frameCount = 10;
+
+ if (!isAlive() && getWalkSpeed().x
+ && Net::getGameHandler()->removeDeadBeings()
+ && static_cast<int> ((static_cast<float>(get_elapsed_time(mActionTime))
+ / static_cast<float>(getWalkSpeed().x))) >= frameCount)
+ {
+ if (mType != PLAYER && actorSpriteManager)
+ actorSpriteManager->destroy(this);
+ }
+
+ const SoundInfo *const sound = mNextSound.sound;
+ if (sound)
+ {
+ const int time2 = tick_time;
+ if (time2 > mNextSound.time)
+ {
+ soundManager.playSfx(sound->sound, mNextSound.x, mNextSound.y);
+
+ mNextSound.sound = nullptr;
+ mNextSound.time = time2 + sound->delay;
+ }
+ }
+
+ BLOCK_END("Being::logic")
+}
+
+void Being::drawEmotion(Graphics *const graphics, const int offsetX,
+ const int offsetY)
+{
+ const int px = getPixelX() - offsetX - 16;
+ const int py = getPixelY() - offsetY - 64 - 32;
+ if (mEmotionSprite)
+ mEmotionSprite->draw(graphics, px, py);
+ if (mAnimationEffect)
+ mAnimationEffect->draw(graphics, px, py);
+}
+
+void Being::drawSpeech(const int offsetX, const int offsetY)
+{
+ if (!mSpeechBubble || mSpeech.empty())
+ return;
+
+ const int px = getPixelX() - offsetX;
+ const int py = getPixelY() - offsetY;
+ const int speech = mSpeechType;
+
+ // Draw speech above this being
+ if (mSpeechTime == 0)
+ {
+ if (mSpeechBubble->isVisible())
+ mSpeechBubble->setVisible(false);
+ }
+ else if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE ||
+ speech == NO_NAME_IN_BUBBLE))
+ {
+ const bool isShowName = (speech == NAME_IN_BUBBLE);
+
+ delete mText;
+ mText = nullptr;
+
+ mSpeechBubble->setCaption(isShowName ? mName : "");
+
+ mSpeechBubble->setText(mSpeech, isShowName);
+ mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
+ py - getHeight() - (mSpeechBubble->getHeight()));
+ mSpeechBubble->setVisible(true);
+ }
+ else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD)
+ {
+ mSpeechBubble->setVisible(false);
+
+ if (!mText && userPalette)
+ {
+ mText = new Text(mSpeech, getPixelX(), getPixelY() - getHeight(),
+ gcn::Graphics::CENTER, &Theme::getThemeColor(
+ Theme::BUBBLE_TEXT), true);
+ }
+ }
+ else if (speech == NO_SPEECH)
+ {
+ mSpeechBubble->setVisible(false);
+
+ delete mText;
+ mText = nullptr;
+ }
+}
+
+int Being::getOffset(const signed char pos, const signed char neg) const
+{
+ // Check whether we're walking in the requested direction
+ if (mAction != MOVE || !(mDirection & (pos | neg)))
+ return 0;
+
+ int offset = 0;
+
+ if (mMap)
+ {
+ const int time = get_elapsed_time(mActionTime);
+ offset = (pos == LEFT && neg == RIGHT) ?
+ static_cast<int>((static_cast<float>(time)
+ * static_cast<float>(mMap->getTileWidth()))
+ / static_cast<float>(mWalkSpeed.x)) :
+ static_cast<int>((static_cast<float>(time)
+ * static_cast<float>(mMap->getTileHeight()))
+ / static_cast<float>(mWalkSpeed.y));
+ }
+
+ // We calculate the offset _from_ the _target_ location
+ offset -= 32;
+ if (offset > 0)
+ offset = 0;
+
+ // Going into negative direction? Invert the offset.
+ if (mDirection & pos)
+ offset = -offset;
+
+ if (offset > 32)
+ offset = 32;
+ if (offset < -32)
+ offset = -32;
+
+ return offset;
+}
+
+void Being::updateCoords()
+{
+ if (!mDispName)
+ return;
+
+ int offsetX = getPixelX();
+ int offsetY = getPixelY();
+ if (mInfo)
+ {
+ offsetX += mInfo->getNameOffsetX();
+ offsetY += mInfo->getNameOffsetY();
+ }
+ // Monster names show above the sprite instead of below it
+ if (mType == MONSTER)
+ offsetY += - getHeight() - mDispName->getHeight();
+
+ mDispName->adviseXY(offsetX, offsetY, mMoveNames);
+}
+
+void Being::optionChanged(const std::string &value)
+{
+ if (mType == PLAYER && value == "visiblenames")
+ setShowName(config.getBoolValue("visiblenames"));
+}
+
+void Being::flashName(const int time)
+{
+ if (mDispName)
+ mDispName->flash(time);
+}
+
+std::string Being::getGenderSignWithSpace() const
+{
+ const std::string &str = getGenderSign();
+ if (str.empty())
+ return str;
+ else
+ return std::string(" ").append(str);
+}
+
+std::string Being::getGenderSign() const
+{
+ std::string str;
+ if (mShowGender)
+ {
+ if (getGender() == GENDER_FEMALE)
+ str = "\u2640";
+ else if (getGender() == GENDER_MALE)
+ str = "\u2642";
+ }
+ if (mShowPlayersStatus && mAdvanced)
+ {
+ if (mShop)
+ str.append("$");
+ if (mAway)
+ {
+ // TRANSLATORS: this away status writed in player nick
+ str.append(_("A"));
+ }
+ else if (mInactive)
+ {
+ // TRANSLATORS: this inactive status writed in player nick
+ str.append(_("I"));
+ }
+ }
+ return str;
+}
+
+void Being::showName()
+{
+ if (mName.empty())
+ return;
+
+ delete mDispName;
+ mDispName = nullptr;
+
+ if (mHideErased && player_relations.getRelation(mName) ==
+ PlayerRelation::ERASED)
+ {
+ return;
+ }
+
+ std::string displayName(mName);
+
+ if (mType != MONSTER && (mShowGender || mShowLevel))
+ {
+ displayName.append(" ");
+ if (mShowLevel && getLevel() != 0)
+ displayName.append(toString(getLevel()));
+
+ displayName.append(getGenderSign());
+ }
+
+ if (mType == MONSTER)
+ {
+ if (config.getBoolValue("showMonstersTakedDamage"))
+ displayName.append(", ").append(toString(getDamageTaken()));
+ }
+
+ gcn::Font *font = nullptr;
+ if (player_node && player_node->getTarget() == this
+ && mType != MONSTER)
+ {
+ font = boldFont;
+ }
+ else if (mType == PLAYER && !player_relations.isGoodName(this) && gui)
+ {
+ font = gui->getSecureFont();
+ }
+
+ if (mInfo)
+ {
+ mDispName = new FlashText(displayName,
+ getPixelX() + mInfo->getNameOffsetX(),
+ getPixelY() + mInfo->getNameOffsetY(),
+ gcn::Graphics::CENTER, mNameColor, font);
+ }
+ else
+ {
+ mDispName = new FlashText(displayName, getPixelX(), getPixelY(),
+ gcn::Graphics::CENTER, mNameColor, font);
+ }
+
+ updateCoords();
+}
+
+void Being::updateColors()
+{
+ if (userPalette)
+ {
+ if (mType == MONSTER)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::MONSTER);
+ mTextColor = &userPalette->getColor(UserPalette::MONSTER);
+ }
+ else if (mType == NPC)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::NPC);
+ mTextColor = &userPalette->getColor(UserPalette::NPC);
+ }
+ else if (this == player_node)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::SELF);
+ mTextColor = &Theme::getThemeColor(Theme::PLAYER);
+ }
+ else
+ {
+ mTextColor = &Theme::getThemeColor(Theme::PLAYER);
+
+ if (player_relations.getRelation(mName) != PlayerRelation::ERASED)
+ mErased = false;
+ else
+ mErased = true;
+
+ if (mIsGM)
+ {
+ mTextColor = &userPalette->getColor(UserPalette::GM);
+ mNameColor = &userPalette->getColor(UserPalette::GM);
+ }
+ else if (mEnemy)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::MONSTER);
+ }
+ else if (mParty && mParty == player_node->getParty())
+ {
+ mNameColor = &userPalette->getColor(UserPalette::PARTY);
+ }
+ else if (player_node && getGuild()
+ && getGuild() == player_node->getGuild())
+ {
+ mNameColor = &userPalette->getColor(UserPalette::GUILD);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::FRIEND)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::FRIEND);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::DISREGARDED
+ || player_relations.getRelation(mName) ==
+ PlayerRelation::BLACKLISTED)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::DISREGARDED);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::IGNORED
+ || player_relations.getRelation(mName) ==
+ PlayerRelation::ENEMY2)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::IGNORED);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::ERASED)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::ERASED);
+ }
+ else
+ {
+ mNameColor = &userPalette->getColor(UserPalette::PC);
+ }
+ }
+
+ if (mDispName)
+ mDispName->setColor(mNameColor);
+ }
+}
+
+void Being::setSprite(const unsigned int slot, const int id,
+ std::string color, const unsigned char colorId,
+ const bool isWeapon, const bool isTempSprite)
+{
+ if (slot >= Net::getCharServerHandler()->maxSprite())
+ return;
+
+ if (slot >= size())
+ ensureSize(slot + 1);
+
+ if (slot >= mSpriteIDs.size())
+ mSpriteIDs.resize(slot + 1, 0);
+
+ if (slot >= mSpriteColors.size())
+ mSpriteColors.resize(slot + 1, "");
+
+ if (slot >= mSpriteColorsIds.size())
+ mSpriteColorsIds.resize(slot + 1, 1);
+
+ // id = 0 means unequip
+ if (id == 0)
+ {
+ removeSprite(slot);
+
+ if (isWeapon)
+ mEquippedWeapon = nullptr;
+ const int id1 = mSpriteIDs[slot];
+ if (id1)
+ {
+ const ItemInfo &info = ItemDB::get(id1);
+ if (mMap)
+ {
+ const int pet = info.getPet();
+ if (pet)
+ removePet();
+ }
+ }
+ }
+ else
+ {
+ const ItemInfo &info = ItemDB::get(id);
+ const std::string filename = info.getSprite(mGender, mSubType);
+ AnimatedSprite *equipmentSprite = nullptr;
+
+ if (mType == PLAYER)
+ {
+ const int pet = info.getPet();
+ if (pet)
+ addPet(pet);
+ }
+
+ if (!filename.empty())
+ {
+ if (color.empty())
+ color = info.getDyeColorsString(colorId);
+
+ equipmentSprite = AnimatedSprite::delayedLoad(
+ paths.getStringValue("sprites").append(
+ combineDye(filename, color)));
+ }
+
+ if (equipmentSprite)
+ equipmentSprite->setSpriteDirection(getSpriteDirection());
+
+ CompoundSprite::setSprite(slot, equipmentSprite);
+
+ if (isWeapon)
+ mEquippedWeapon = &ItemDB::get(id);
+
+ setAction(mAction);
+ }
+
+ if (!isTempSprite)
+ {
+ mSpriteIDs[slot] = id;
+ mSpriteColors[slot] = color;
+ mSpriteColorsIds[slot] = colorId;
+ recalcSpritesOrder();
+ if (beingEquipmentWindow)
+ beingEquipmentWindow->updateBeing(this);
+ }
+}
+
+void Being::setSpriteID(const unsigned int slot, const int id)
+{
+ setSprite(slot, id, mSpriteColors[slot]);
+}
+
+void Being::setSpriteColor(const unsigned int slot, const std::string &color)
+{
+ setSprite(slot, mSpriteIDs[slot], color);
+}
+
+void Being::setHairStyle(const unsigned int slot, const int id)
+{
+// dumpSprites();
+ setSprite(slot, id, ItemDB::get(id).getDyeColorsString(mHairColor));
+// dumpSprites();
+}
+
+void Being::setHairColor(const unsigned int slot, const unsigned char color)
+{
+ mHairColor = color;
+ setSprite(slot, mSpriteIDs[slot], ItemDB::get(
+ getSpriteID(slot)).getDyeColorsString(color));
+}
+
+void Being::dumpSprites() const
+{
+ std::vector<int>::const_iterator it1 = mSpriteIDs.begin();
+ const std::vector<int>::const_iterator it1_end = mSpriteIDs.end();
+ StringVectCIter it2 = mSpriteColors.begin();
+ const StringVectCIter it2_end = mSpriteColors.end();
+ std::vector<int>::const_iterator it3 = mSpriteColorsIds.begin();
+ const std::vector<int>::const_iterator it3_end = mSpriteColorsIds.end();
+
+ logger->log("sprites");
+ for (; it1 != it1_end && it2 != it2_end && it3 != it3_end;
+ ++ it1, ++ it2, ++ it3)
+ {
+ logger->log("%d,%s,%d", *it1, (*it2).c_str(), *it3);
+ }
+}
+
+void Being::load()
+{
+ // Hairstyles are encoded as negative numbers. Count how far negative
+ // we can go.
+ int hairstyles = 1;
+ while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE, 0) !=
+ paths.getStringValue("spriteErrorFile"))
+ {
+ hairstyles ++;
+ }
+ mNumberOfHairstyles = hairstyles;
+
+ int races = 100;
+ while (ItemDB::get(-races).getSprite(GENDER_MALE, 0) !=
+ paths.getStringValue("spriteErrorFile"))
+ {
+ races ++;
+ }
+ mNumberOfRaces = races - 100;
+}
+
+void Being::updateName()
+{
+ if (mShowName)
+ showName();
+}
+
+void Being::reReadConfig()
+{
+ BLOCK_START("Being::reReadConfig")
+ if (mUpdateConfigTime + 1 < cur_time)
+ {
+ mAwayEffect = paths.getIntValue("afkEffectId");
+ mHighlightMapPortals = config.getBoolValue("highlightMapPortals");
+ mConfLineLim = config.getIntValue("chatMaxCharLimit");
+ mSpeechType = config.getIntValue("speech");
+ mHighlightMonsterAttackRange =
+ config.getBoolValue("highlightMonsterAttackRange");
+ mLowTraffic = config.getBoolValue("lowTraffic");
+ mDrawHotKeys = config.getBoolValue("drawHotKeys");
+ mShowBattleEvents = config.getBoolValue("showBattleEvents");
+ mShowMobHP = config.getBoolValue("showMobHP");
+ mShowOwnHP = config.getBoolValue("showOwnHP");
+ mShowGender = config.getBoolValue("showgender");
+ mShowLevel = config.getBoolValue("showlevel");
+ mShowPlayersStatus = config.getBoolValue("showPlayersStatus");
+ mEnableReorderSprites = config.getBoolValue("enableReorderSprites");
+ mHideErased = config.getBoolValue("hideErased");
+ mMoveNames = config.getBoolValue("moveNames");
+
+ mUpdateConfigTime = cur_time;
+ }
+ BLOCK_END("Being::reReadConfig")
+}
+
+bool Being::updateFromCache()
+{
+ const BeingCacheEntry *const entry = Being::getCacheEntry(getId());
+
+ if (entry && entry->getTime() + 120 >= cur_time)
+ {
+ if (!entry->getName().empty())
+ setName(entry->getName());
+ setPartyName(entry->getPartyName());
+ setGuildName(entry->getGuildName());
+ setLevel(entry->getLevel());
+ setPvpRank(entry->getPvpRank());
+ setIp(entry->getIp());
+
+ mAdvanced = entry->isAdvanced();
+ if (mAdvanced)
+ {
+ const int flags = entry->getFlags();
+ mShop = ((flags & FLAG_SHOP) != 0);
+ mAway = ((flags & FLAG_AWAY) != 0);
+ mInactive = ((flags & FLAG_INACTIVE) != 0);
+ if (mShop || mAway || mInactive)
+ updateName();
+ }
+ else
+ {
+ mShop = false;
+ mAway = false;
+ mInactive = false;
+ }
+
+ updateAwayEffect();
+ if (mType == PLAYER)
+ updateColors();
+ return true;
+ }
+ return false;
+}
+
+void Being::addToCache() const
+{
+ if (player_node == this)
+ return;
+
+ BeingCacheEntry *entry = Being::getCacheEntry(getId());
+ if (!entry)
+ {
+ entry = new BeingCacheEntry(getId());
+ beingInfoCache.push_front(entry);
+
+ if (beingInfoCache.size() >= CACHE_SIZE)
+ {
+ delete beingInfoCache.back();
+ beingInfoCache.pop_back();
+ }
+ }
+ if (!mLowTraffic)
+ return;
+
+ entry->setName(getName());
+ entry->setLevel(getLevel());
+ entry->setPartyName(getPartyName());
+ entry->setGuildName(getGuildName());
+ entry->setTime(cur_time);
+ entry->setPvpRank(getPvpRank());
+ entry->setIp(getIp());
+ entry->setAdvanced(isAdvanced());
+ if (isAdvanced())
+ {
+ int flags = 0;
+ if (mShop)
+ flags += FLAG_SHOP;
+ if (mAway)
+ flags += FLAG_AWAY;
+ if (mInactive)
+ flags += FLAG_INACTIVE;
+ entry->setFlags(flags);
+ }
+ else
+ {
+ entry->setFlags(0);
+ }
+}
+
+BeingCacheEntry* Being::getCacheEntry(const int id)
+{
+ FOR_EACH (std::list<BeingCacheEntry*>::iterator, i, beingInfoCache)
+ {
+ if (!*i)
+ continue;
+
+ if (id == (*i)->getId())
+ {
+ // Raise priority: move it to front
+ if ((*i)->getTime() + 120 < cur_time)
+ {
+ beingInfoCache.splice(beingInfoCache.begin(),
+ beingInfoCache, i);
+ }
+ return *i;
+ }
+ }
+ return nullptr;
+}
+
+
+void Being::setGender(const Gender gender)
+{
+ if (gender != mGender)
+ {
+ mGender = gender;
+
+ // Reload all subsprites
+ for (unsigned int i = 0; i < mSpriteIDs.size(); i++)
+ {
+ if (mSpriteIDs.at(i) != 0)
+ setSprite(i, mSpriteIDs.at(i), mSpriteColors.at(i));
+ }
+
+ updateName();
+ }
+}
+
+void Being::setGM(const bool gm)
+{
+ mIsGM = gm;
+
+ updateColors();
+}
+
+void Being::talkTo() const
+{
+ if (!client->limitPackets(PACKET_NPC_TALK))
+ return;
+
+ Net::getNpcHandler()->talk(mId);
+}
+
+bool Being::draw(Graphics *const graphics,
+ const int offsetX, const int offsetY) const
+{
+ bool res = true;
+ if (!mErased)
+ res = ActorSprite::draw(graphics, offsetX, offsetY);
+
+ return res;
+}
+
+void Being::drawSprites(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ const int sz = getNumberOfLayers();
+ for (int f = 0; f < sz; f ++)
+ {
+ const int rSprite = mSpriteHide[mSpriteRemap[f]];
+ if (rSprite == 1)
+ continue;
+
+ Sprite *const sprite = getSprite(mSpriteRemap[f]);
+ if (sprite)
+ {
+ sprite->setAlpha(mAlpha);
+ sprite->draw(graphics, posX, posY);
+ }
+ }
+}
+
+void Being::drawSpritesSDL(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ const size_t sz = size();
+ for (unsigned f = 0; f < sz; f ++)
+ {
+ const int rSprite = mSpriteHide[mSpriteRemap[f]];
+ if (rSprite == 1)
+ continue;
+
+ const Sprite *const sprite = getSprite(mSpriteRemap[f]);
+ if (sprite)
+ sprite->draw(graphics, posX, posY);
+ }
+}
+
+bool Being::drawSpriteAt(Graphics *const graphics,
+ const int x, const int y) const
+{
+ bool res = true;
+
+ if (!mErased)
+ res = ActorSprite::drawSpriteAt(graphics, x, y);
+
+ if (!userPalette)
+ return res;
+
+ if (mHighlightMapPortals && mMap && mSubType == 45 && !mMap->getHasWarps())
+ {
+ graphics->setColor(userPalette->
+ getColorWithAlpha(UserPalette::PORTAL_HIGHLIGHT));
+
+ graphics->fillRectangle(gcn::Rectangle(x, y, 32, 32));
+
+ if (mDrawHotKeys && !mName.empty())
+ {
+ gcn::Font *const font = gui->getFont();
+ if (font)
+ {
+ graphics->setColor(userPalette->getColor(UserPalette::BEING));
+ font->drawString(graphics, mName, x, y);
+ }
+ }
+ }
+
+ if (mHighlightMonsterAttackRange && mType == ActorSprite::MONSTER
+ && isAlive())
+ {
+ int attackRange;
+ if (mAttackRange)
+ attackRange = 32 * mAttackRange;
+ else
+ attackRange = 32;
+
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::MONSTER_ATTACK_RANGE));
+
+ graphics->fillRectangle(gcn::Rectangle(
+ x - attackRange, y - attackRange,
+ 2 * attackRange + 32, 2 * attackRange + 32));
+ }
+
+ if (mShowMobHP && mInfo && player_node && player_node->getTarget() == this
+ && mType == MONSTER)
+ {
+ // show hp bar here
+ int maxHP = mMaxHP;
+ if (!maxHP)
+ maxHP = mInfo->getMaxHP();
+
+ drawHpBar(graphics, maxHP, mHP, mDamageTaken,
+ UserPalette::MONSTER_HP, UserPalette::MONSTER_HP2,
+ x - 50 + 16 + mInfo->getHpBarOffsetX(),
+ y + 32 - 6 + mInfo->getHpBarOffsetY(),
+ 2 * 50, 4);
+ }
+ if (mShowOwnHP && mInfo && player_node == this && mAction != DEAD)
+ {
+ drawHpBar(graphics, PlayerInfo::getAttribute(PlayerInfo::MAX_HP),
+ PlayerInfo::getAttribute(PlayerInfo::HP), 0,
+ UserPalette::PLAYER_HP, UserPalette::PLAYER_HP2,
+ x - 50 + 16 + mInfo->getHpBarOffsetX(),
+ y + 32 - 6 + mInfo->getHpBarOffsetY(),
+ 2 * 50, 4);
+ }
+ return res;
+}
+
+void Being::drawHpBar(Graphics *const graphics, const int maxHP, const int hp,
+ const int damage, const int color1, const int color2,
+ const int x, const int y, const int width,
+ const int height) const
+{
+ if (maxHP <= 0 || !userPalette)
+ return;
+
+ float p;
+
+ if (hp)
+ {
+ p = static_cast<float>(maxHP) / static_cast<float>(hp);
+ }
+ else if (maxHP != damage)
+ {
+ p = static_cast<float>(maxHP)
+ / static_cast<float>(maxHP - damage);
+ }
+ else
+ {
+ p = 1;
+ }
+
+ if (p <= 0 || p > width)
+ return;
+
+ const int dx = static_cast<const int>(static_cast<float>(width) / p);
+
+ if (serverVersion < 1)
+ { // old servers
+ if ((!damage && (this != player_node || hp == maxHP))
+ || (!hp && maxHP == damage))
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color1));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, dx, height));
+ return;
+ }
+ else if (width - dx <= 0)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color2));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, width, height));
+ return;
+ }
+ }
+ else
+ { // evol servers
+ if (hp == maxHP)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color1));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, dx, height));
+ return;
+ }
+ else if (width - dx <= 0)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color2));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, width, height));
+ return;
+ }
+ }
+
+ graphics->setColor(userPalette->getColorWithAlpha(color1));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, dx, height));
+
+ graphics->setColor(userPalette->getColorWithAlpha(color2));
+ graphics->fillRectangle(gcn::Rectangle(
+ x + dx, y, width - dx, height));
+}
+
+void Being::setHP(const int hp)
+{
+ mHP = hp;
+ if (mMaxHP < mHP)
+ mMaxHP = mHP;
+ if (mType == MONSTER)
+ updatePercentHP();
+}
+
+void Being::setMaxHP(const int hp)
+{
+ mMaxHP = hp;
+ if (mMaxHP < mHP)
+ mMaxHP = mHP;
+}
+
+void Being::resetCounters()
+{
+ mMoveTime = 0;
+ mAttackTime = 0;
+ mTalkTime = 0;
+ mOtherTime = 0;
+ mTestTime = cur_time;
+}
+
+void Being::recalcSpritesOrder()
+{
+ if (!mEnableReorderSprites)
+ return;
+
+// logger->log("recalcSpritesOrder");
+ const unsigned sz = static_cast<unsigned>(size());
+ if (sz < 1)
+ return;
+
+ std::vector<int> slotRemap;
+ std::map<int, int> itemSlotRemap;
+
+ std::vector<int>::iterator it;
+ int oldHide[20];
+ int dir = mSpriteDirection;
+ if (dir < 0 || dir >= 9)
+ dir = 0;
+ // hack for allow different logic in dead player
+ if (mAction == DEAD)
+ dir = 9;
+
+ const unsigned int hairSlot = Net::getCharServerHandler()->hairSprite();
+
+ for (unsigned slot = 0; slot < sz; slot ++)
+ {
+ oldHide[slot] = mSpriteHide[slot];
+ mSpriteHide[slot] = 0;
+ }
+
+ const size_t spriteIdSize = mSpriteIDs.size();
+ for (unsigned slot = 0; slot < sz; slot ++)
+ {
+ slotRemap.push_back(slot);
+
+ if (spriteIdSize <= slot)
+ continue;
+
+ const int id = mSpriteIDs[slot];
+ if (!id)
+ continue;
+
+ const ItemInfo &info = ItemDB::get(id);
+
+ if (info.isRemoveSprites())
+ {
+ SpriteToItemMap *const spriteToItems
+ = info.getSpriteToItemReplaceMap(dir);
+
+ if (spriteToItems)
+ {
+ FOR_EACHP (SpriteToItemMapCIter, itr, spriteToItems)
+ {
+ const int remSprite = itr->first;
+ const std::map<int, int> &itemReplacer = itr->second;
+ if (remSprite >= 0)
+ { // slot known
+ if (itemReplacer.empty())
+ {
+ mSpriteHide[remSprite] = 1;
+ }
+ else
+ {
+ std::map<int, int>::const_iterator repIt
+ = itemReplacer.find(mSpriteIDs[remSprite]);
+ if (repIt == itemReplacer.end())
+ {
+ repIt = itemReplacer.find(0);
+ if (repIt->second == 0)
+ repIt = itemReplacer.end();
+ }
+ if (repIt != itemReplacer.end())
+ {
+ mSpriteHide[remSprite] = repIt->second;
+ if (repIt->second != 1)
+ {
+ if (static_cast<unsigned>(remSprite)
+ != hairSlot)
+ {
+ setSprite(remSprite, repIt->second,
+ mSpriteColors[remSprite],
+ 1, false, true);
+ }
+ else
+ {
+ setSprite(remSprite, repIt->second,
+ ItemDB::get(repIt->second)
+ .getDyeColorsString(mHairColor),
+ 1, false, true);
+ }
+ }
+ }
+ }
+ }
+ else
+ { // slot unknown. Search for real slot, this can be slow
+ FOR_EACH (IntMapCIter, repIt, itemReplacer)
+ {
+ for (unsigned slot2 = 0; slot2 < sz; slot2 ++)
+ {
+ if (mSpriteIDs[slot2] == repIt->first)
+ {
+ mSpriteHide[slot2] = repIt->second;
+ if (repIt->second != 1)
+ {
+ if (slot2 != hairSlot)
+ {
+ setSprite(slot2, repIt->second,
+ mSpriteColors[slot2],
+ 1, false, true);
+ }
+ else
+ {
+ setSprite(slot2, repIt->second,
+ ItemDB::get(repIt->second)
+ .getDyeColorsString(
+ mHairColor),
+ 1, false, true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (info.mDrawBefore[dir] > 0)
+ {
+ const int id2 = mSpriteIDs[info.mDrawBefore[dir]];
+ if (itemSlotRemap.find(id2) != itemSlotRemap.end())
+ {
+// logger->log("found duplicate (before)");
+ const ItemInfo &info2 = ItemDB::get(id2);
+ if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
+ {
+// logger->log("old more priority");
+ continue;
+ }
+ else
+ {
+// logger->log("new more priority");
+ itemSlotRemap.erase(id2);
+ }
+ }
+
+ itemSlotRemap[id] = -info.mDrawBefore[dir];
+ }
+ else if (info.mDrawAfter[dir] > 0)
+ {
+ const int id2 = mSpriteIDs[info.mDrawAfter[dir]];
+ if (itemSlotRemap.find(id2) != itemSlotRemap.end())
+ {
+ const ItemInfo &info2 = ItemDB::get(id2);
+ if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
+ {
+// logger->log("old more priority");
+ continue;
+ }
+ else
+ {
+// logger->log("new more priority");
+ itemSlotRemap.erase(id2);
+ }
+ }
+
+ itemSlotRemap[id] = info.mDrawAfter[dir];
+// logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]);
+ }
+ }
+// logger->log("preparation end");
+
+ int lastRemap = 0;
+ unsigned cnt = 0;
+
+ while (cnt < 15 && lastRemap >= 0)
+ {
+ lastRemap = -1;
+ cnt ++;
+// logger->log("iteration");
+
+ for (unsigned slot0 = 0; slot0 < sz; slot0 ++)
+ {
+ const int slot = searchSlotValue(slotRemap, slot0);
+ const int val = slotRemap.at(slot);
+ int id = 0;
+
+ if (static_cast<int>(spriteIdSize) > val)
+ id = mSpriteIDs[val];
+
+ int idx = -1;
+ int idx1 = -1;
+// logger->log("item %d, id=%d", slot, id);
+ int reorder = 0;
+ const std::map<int, int>::const_iterator
+ orderIt = itemSlotRemap.find(id);
+ if (orderIt != itemSlotRemap.end())
+ reorder = orderIt->second;
+
+ if (reorder < 0)
+ {
+// logger->log("move item %d before %d", slot, -reorder);
+ searchSlotValueItr(it, idx, slotRemap, -reorder);
+ if (it == slotRemap.end())
+ return;
+ searchSlotValueItr(it, idx1, slotRemap, val);
+ if (it == slotRemap.end())
+ return;
+ lastRemap = idx1;
+ if (idx1 + 1 != idx)
+ {
+ slotRemap.erase(it);
+ searchSlotValueItr(it, idx, slotRemap, -reorder);
+ slotRemap.insert(it, val);
+ }
+ }
+ else if (reorder > 0)
+ {
+// logger->log("move item %d after %d", slot, reorder);
+ searchSlotValueItr(it, idx, slotRemap, reorder);
+ searchSlotValueItr(it, idx1, slotRemap, val);
+ if (it == slotRemap.end())
+ return;
+ lastRemap = idx1;
+ if (idx1 != idx + 1)
+ {
+ slotRemap.erase(it);
+ searchSlotValueItr(it, idx, slotRemap, reorder);
+ if (it != slotRemap.end())
+ {
+ ++ it;
+ if (it != slotRemap.end())
+ slotRemap.insert(it, val);
+ else
+ slotRemap.push_back(val);
+ }
+ else
+ {
+ slotRemap.push_back(val);
+ }
+ }
+ }
+ }
+ }
+
+// logger->log("after remap");
+ for (unsigned slot = 0; slot < sz; slot ++)
+ {
+ mSpriteRemap[slot] = slotRemap[slot];
+ if (oldHide[slot] != 0 && oldHide[slot] != 1 && mSpriteHide[slot] == 0)
+ {
+ const int id = mSpriteIDs[slot];
+ if (!id)
+ continue;
+
+ setSprite(slot, id, mSpriteColors[slot], 1, false, true);
+ }
+// logger->log("slot %d = %d", slot, mSpriteRemap[slot]);
+ }
+}
+
+int Being::searchSlotValue(const std::vector<int> &slotRemap,
+ const int val) const
+{
+ const size_t sz = size();
+ for (size_t slot = 0; slot < sz; slot ++)
+ {
+ if (slotRemap[slot] == val)
+ return slot;
+ }
+ return getNumberOfLayers() - 1;
+}
+
+void Being::searchSlotValueItr(std::vector<int>::iterator &it, int &idx,
+ std::vector<int> &slotRemap,
+ const int val) const
+{
+// logger->log("searching %d", val);
+ it = slotRemap.begin();
+ const std::vector<int>::iterator it_end = slotRemap.end();
+ idx = 0;
+ while (it != it_end)
+ {
+// logger->log("testing %d", *it);
+ if (*it == val)
+ {
+// logger->log("found at %d", idx);
+ return;
+ }
+ ++ it;
+ idx ++;
+ }
+// logger->log("not found");
+ idx = -1;
+ return;
+}
+
+void Being::updateHit(const int amount)
+{
+ if (amount > 0)
+ {
+ if (!mMinHit || amount < mMinHit)
+ mMinHit = amount;
+ if (amount != mCriticalHit && (!mMaxHit || amount > mMaxHit))
+ mMaxHit = amount;
+ }
+}
+
+Equipment *Being::getEquipment()
+{
+ Equipment *const eq = new Equipment();
+ Equipment::Backend *const bk = new BeingEquipBackend(this);
+ eq->setBackend(bk);
+ return eq;
+}
+
+void Being::undressItemById(const int id)
+{
+ const size_t sz = mSpriteIDs.size();
+
+ for (size_t f = 0; f < sz; f ++)
+ {
+ if (id == mSpriteIDs[f])
+ {
+ setSprite(static_cast<unsigned int>(f), 0);
+ break;
+ }
+ }
+}
+
+void Being::clearCache()
+{
+ delete_all(beingInfoCache);
+ beingInfoCache.clear();
+}
+
+void Being::updateComment()
+{
+ if (mGotComment || mName.empty())
+ return;
+
+ mGotComment = true;
+ mComment = loadComment(mName, mType);
+}
+
+std::string Being::loadComment(const std::string &name, const int type)
+{
+ std::string str;
+ switch (type)
+ {
+ case PLAYER:
+ str = client->getUsersDirectory();
+ break;
+ case NPC:
+ str = client->getNpcsDirectory();
+ break;
+ default:
+ return "";
+ }
+
+ str.append(stringToHexPath(name)).append("/comment.txt");
+ logger->log("load from: %s", str.c_str());
+ StringVect lines;
+
+ const ResourceManager *const resman = ResourceManager::getInstance();
+ if (resman->existsLocal(str))
+ {
+ lines = resman->loadTextFileLocal(str);
+ if (lines.size() >= 2)
+ return lines[1];
+ }
+ return "";
+}
+
+void Being::saveComment(const std::string &name,
+ const std::string &comment, const int type)
+{
+ std::string dir;
+ switch (type)
+ {
+ case PLAYER:
+ dir = client->getUsersDirectory();
+ break;
+ case NPC:
+ dir = client->getNpcsDirectory();
+ break;
+ default:
+ return;
+ }
+ dir.append(stringToHexPath(name));
+ const ResourceManager *const resman = ResourceManager::getInstance();
+ resman->saveTextFile(dir, "comment.txt",
+ (name + "\n").append(comment));
+}
+
+void Being::setState(const uint8_t state)
+{
+ const bool shop = ((state & FLAG_SHOP) != 0);
+ const bool away = ((state & FLAG_AWAY) != 0);
+ const bool inactive = ((state & FLAG_INACTIVE) != 0);
+ const bool needUpdate = (shop != mShop || away != mAway
+ || inactive != mInactive);
+
+ mShop = shop;
+ mAway = away;
+ mInactive = inactive;
+ updateAwayEffect();
+
+ if (needUpdate)
+ {
+ if (shop || away || inactive)
+ mAdvanced = true;
+ updateName();
+ addToCache();
+ }
+}
+
+void Being::setEmote(const uint8_t emotion, const int emote_time)
+{
+ if ((emotion & FLAG_SPECIAL) == FLAG_SPECIAL)
+ {
+ setState(emotion);
+ mAdvanced = true;
+ }
+ else
+ {
+ const int emotionIndex = emotion - 1;
+ if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast())
+ {
+ delete mEmotionSprite;
+ mEmotionSprite = nullptr;
+ const EmoteInfo *const info = EmoteDB::get2(emotionIndex, true);
+ if (info)
+ {
+ const EmoteSprite *const sprite = info->sprites.front();
+ if (sprite)
+ {
+ mEmotionSprite = AnimatedSprite::clone(sprite->sprite);
+ if (mEmotionSprite)
+ mEmotionTime = info->time;
+ else
+ mEmotionTime = emote_time;
+ }
+ }
+ }
+
+ if (mEmotionSprite)
+ {
+ mEmotionSprite->play(mSpriteAction);
+ mEmotionSprite->setSpriteDirection(static_cast<SpriteDirection>(
+ mSpriteDirection));
+ }
+ else
+ {
+ mEmotionTime = 0;
+ }
+ }
+}
+
+void Being::updatePercentHP()
+{
+ if (!mMaxHP || !serverVersion)
+ return;
+ if (mHP)
+ {
+ const unsigned num = mHP * 100 / mMaxHP;
+ if (num != mNumber)
+ {
+ mNumber = num;
+ if (updateNumber(mNumber))
+ setAction(mAction);
+ }
+ }
+}
+
+uint8_t Being::genderToInt(const Gender sex)
+{
+ switch (sex)
+ {
+ case GENDER_FEMALE:
+ case GENDER_UNSPECIFIED:
+ default:
+ return 0;
+ case GENDER_MALE:
+ return 1;
+ case GENDER_OTHER:
+ return 3;
+ }
+}
+
+Gender Being::intToGender(const uint8_t sex)
+{
+ switch (sex)
+ {
+ case 0:
+ default:
+ return GENDER_FEMALE;
+ case 1:
+ return GENDER_MALE;
+ case 3:
+ return GENDER_OTHER;
+ }
+}
+
+int Being::getSpriteID(const int slot) const
+{
+ if (slot < 0 || static_cast<unsigned>(slot) >= mSpriteIDs.size())
+ return -1;
+
+ return mSpriteIDs[slot];
+}
+
+void Being::addAfkEffect()
+{
+ addSpecialEffect(mAwayEffect);
+}
+
+void Being::removeAfkEffect()
+{
+ removeSpecialEffect();
+}
+
+void Being::addSpecialEffect(const int effect)
+{
+ if (effectManager && Particle::enabled
+ && !mSpecialParticle && effect != -1)
+ {
+ mSpecialParticle = effectManager->triggerReturn(effect, this);
+ }
+}
+
+void Being::removeSpecialEffect()
+{
+ if (effectManager && mSpecialParticle)
+ {
+ mChildParticleEffects.removeLocally(mSpecialParticle);
+ mSpecialParticle = nullptr;
+ }
+ delete mAnimationEffect;
+ mAnimationEffect = nullptr;
+}
+
+void Being::updateAwayEffect()
+{
+ if (mAway)
+ addAfkEffect();
+ else
+ removeAfkEffect();
+}
+
+void Being::addEffect(const std::string &name)
+{
+ delete mAnimationEffect;
+ mAnimationEffect = AnimatedSprite::load(
+ paths.getStringValue("sprites") + name);
+}
+
+void Being::addPet(const int id)
+{
+ if (!actorSpriteManager)
+ return;
+
+ removePet();
+ Being *const being = actorSpriteManager->createBeing(
+ id, ActorSprite::PET, 0);
+ if (being)
+ {
+ being->setTileCoords(getTileX(), getTileY());
+ being->setOwner(this);
+ mPetId = id;
+ mPet = being;
+ }
+}
+
+void Being::removePet()
+{
+ if (!actorSpriteManager)
+ return;
+
+ mPetId = 0;
+ if (mPet)
+ {
+ mPet->setOwner(nullptr);
+ actorSpriteManager->destroy(mPet);
+ mPet = nullptr;
+ }
+}
+
+void Being::updatePets()
+{
+ removePet();
+ FOR_EACH (std::vector<int>::const_iterator, it, mSpriteIDs)
+ {
+ const int id = *it;
+ if (!id)
+ continue;
+ const ItemInfo &info = ItemDB::get(id);
+ const int pet = info.getPet();
+ if (pet)
+ {
+ addPet(pet);
+ return;
+ }
+ }
+}
+
+void Being::playSfx(const SoundInfo &sound, Being *const being,
+ const bool main, const int x, const int y)
+{
+ if (being)
+ {
+ // here need add timer and delay sound
+ const int time = tick_time;
+ if (main)
+ {
+ being->mNextSound.sound = nullptr;
+ being->mNextSound.time = time + sound.delay;
+ soundManager.playSfx(sound.sound, x, y);
+ }
+ else if (mNextSound.time <= time)
+ { // old event sound time is gone. we can play new sound
+ being->mNextSound.sound = nullptr;
+ being->mNextSound.time = time + sound.delay;
+ soundManager.playSfx(sound.sound, x, y);
+ }
+ else
+ { // old event sound in progress. need save sound and wait
+ being->mNextSound.sound = &sound;
+ being->mNextSound.x = x;
+ being->mNextSound.y = y;
+ }
+ }
+ else
+ {
+ soundManager.playSfx(sound.sound, x, y);
+ }
+}
+
+void Being::setLook(const int look)
+{
+ if (mType == PLAYER)
+ setSubtype(mSubType, look);
+}