diff options
author | Andrei Karas <akaras@inbox.ru> | 2013-08-31 22:42:10 +0300 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2013-08-31 22:42:10 +0300 |
commit | 00cda69b883d6354f093be6ee39a7936cb798979 (patch) | |
tree | f1daa290abfb53180bd8420a45fe6dff1c7a2ab3 /src/being/being.cpp | |
parent | 5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829 (diff) | |
download | manaverse-00cda69b883d6354f093be6ee39a7936cb798979.tar.gz manaverse-00cda69b883d6354f093be6ee39a7936cb798979.tar.bz2 manaverse-00cda69b883d6354f093be6ee39a7936cb798979.tar.xz manaverse-00cda69b883d6354f093be6ee39a7936cb798979.zip |
move being related files into being dir.
Diffstat (limited to 'src/being/being.cpp')
-rw-r--r-- | src/being/being.cpp | 3041 |
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); +} |