diff options
Diffstat (limited to 'src/being.cpp')
-rw-r--r-- | src/being.cpp | 764 |
1 files changed, 456 insertions, 308 deletions
diff --git a/src/being.cpp b/src/being.cpp index 5c737c0c..24f2e2e1 100644 --- a/src/being.cpp +++ b/src/being.cpp @@ -26,99 +26,157 @@ #include "configuration.h" #include "effectmanager.h" #include "graphics.h" +#include "guild.h" #include "localplayer.h" #include "log.h" #include "map.h" #include "particle.h" +#include "party.h" #include "simpleanimation.h" #include "sound.h" +#include "sprite.h" #include "text.h" #include "statuseffect.h" +#include "gui/buy.h" +#include "gui/buysell.h" #include "gui/gui.h" +#include "gui/npcdialog.h" +#include "gui/npcpostdialog.h" +#include "gui/sell.h" +#include "gui/socialwindow.h" #include "gui/speechbubble.h" #include "gui/theme.h" #include "gui/userpalette.h" +#include "net/charhandler.h" +#include "net/net.h" +#include "net/npchandler.h" +#include "net/playerhandler.h" + +#include "resources/beinginfo.h" #include "resources/colordb.h" #include "resources/emotedb.h" #include "resources/image.h" #include "resources/itemdb.h" #include "resources/iteminfo.h" +#include "resources/monsterdb.h" +#include "resources/npcdb.h" #include "resources/resourcemanager.h" - #include "utils/dtor.h" #include "utils/stringutils.h" #include "utils/xml.h" -#include "net/net.h" -#include "net/playerhandler.h" #include <cassert> #include <cmath> -#define BEING_EFFECTS_FILE "effects.xml" #define HAIR_FILE "hair.xml" static const int DEFAULT_BEING_WIDTH = 32; static const int DEFAULT_BEING_HEIGHT = 32; - - int Being::mNumberOfHairstyles = 1; // TODO: mWalkTime used by eAthena only -Being::Being(int id, int subtype, Map *map): +Being::Being(int id, Type type, int subtype, Map *map): + ActorSprite(id), + mInfo(BeingInfo::Unknown), mFrame(0), mWalkTime(0), mEmotion(0), mEmotionTime(0), mSpeechTime(0), + mAttackType(1), mAttackSpeed(350), mAction(STAND), - mSubType(subtype), - mId(id), + mSubType(0xFFFF), mDirection(DOWN), mSpriteDirection(DIRECTION_DOWN), - mMap(NULL), mDispName(0), mShowName(false), mEquippedWeapon(NULL), mText(0), - mStunMode(0), - mAlpha(1.0f), - mStatusParticleEffects(&mStunParticleEffects, false), - mChildParticleEffects(&mStatusParticleEffects, false), - mMustResetParticles(false), + mGender(GENDER_UNSPECIFIED), + mParty(NULL), + mIsGM(false), + mType(type), mX(0), mY(0), - mDamageTaken(0), - mUsedTargetCursor(NULL) + mDamageTaken(0) { setMap(map); + setSubtype(subtype); mSpeechBubble = new SpeechBubble; - mNameColor = &userPalette->getColor(UserPalette::NPC); - mTextColor = &Theme::getThemeColor(Theme::CHAT); mWalkSpeed = Net::getPlayerHandler()->getDefaultWalkSpeed(); -} -Being::~Being() -{ - mUsedTargetCursor = NULL; - delete_all(mSprites); + if (getType() == PLAYER) + mShowName = config.getValue("visiblenames", 1); - if (player_node && player_node->getTarget() == this) - player_node->setTarget(NULL); + config.addListener("visiblenames", this); - setMap(NULL); + if (getType() == PLAYER || getType() == NPC) + setShowName(true); + updateColors(); +} + +Being::~Being() +{ delete mSpeechBubble; delete mDispName; delete mText; + + config.removeListener("visiblenames", this); +} + +void Being::setSubtype(Uint16 subtype) +{ + if (subtype == mSubType) + return; + + mSubType = subtype; + + if (getType() == MONSTER) + { + mInfo = MonsterDB::get(mSubType); + setName(mInfo->getName()); + setupSpriteDisplay(mInfo->getDisplay()); + } + else if (getType() == NPC) + { + mInfo = NPCDB::get(mSubType); + setupSpriteDisplay(mInfo->getDisplay(), false); + } + else if (getType() == PLAYER) + { + int id = -100 - subtype; + + // Prevent showing errors when sprite doesn't exist + if (!ItemDB::exists(id)) + id = -100; + + setSprite(Net::getCharHandler()->baseSprite(), id); + } +} + +ActorSprite::TargetCursorSize Being::getTargetCursorSize() const +{ + return mInfo->getTargetCursorSize(); +} + +unsigned char Being::getWalkMask() const +{ + return mInfo->getWalkMask(); +} + +Map::BlockType Being::getBlockType() const +{ + return mInfo->getBlockType(); } void Being::setPosition(const Vector &pos) { - mPos = pos; + Actor::setPosition(pos); updateCoords(); @@ -292,6 +350,8 @@ void Being::takeDamage(Being *attacker, int amount, AttackType type) if (amount > 0) { + sound.playSfx(mInfo->getSound(SOUND_EVENT_HURT)); + if (getType() == MONSTER) { mDamageTaken += amount; @@ -317,35 +377,48 @@ void Being::handleAttack(Being *victim, int damage, AttackType type) fireMissile(victim, mEquippedWeapon->getMissileParticle()); } } + else + fireMissile(victim, mInfo->getAttack(mAttackType)->missileParticle); + if (Net::getNetworkType() == ServerInfo::TMWATHENA) { mFrame = 0; mWalkTime = tick_time; } + + sound.playSfx(mInfo->getSound((damage > 0) ? + SOUND_EVENT_HIT : SOUND_EVENT_MISS)); } void Being::setName(const std::string &name) { - mName = name; - - if (getShowName()) + if (getType() == NPC) + { + mName = name.substr(0, name.find('#', 0)); showName(); + } + else + { + mName = name; + + if (getType() == PLAYER && getShowName()) + showName(); + } } void Being::setShowName(bool doShowName) { - bool oldShow = mShowName; + if (mShowName == doShowName) + return; + mShowName = doShowName; - if (doShowName != oldShow) + if (doShowName) + showName(); + else { - if (doShowName) - showName(); - else - { - delete mDispName; - mDispName = 0; - } + delete mDispName; + mDispName = 0; } } @@ -360,26 +433,99 @@ void Being::setGuildPos(const std::string &pos) logger->log("Got guild position \"%s\" for being %s(%i)", pos.c_str(), mName.c_str(), mId); } -void Being::setMap(Map *map) +void Being::addGuild(Guild *guild) { - // Remove sprite from potential previous map - if (mMap) - mMap->removeSprite(mMapSprite); + mGuilds[guild->getId()] = guild; + guild->addMember(mId, mName); + + if (this == player_node && socialWindow) + { + socialWindow->addTab(guild); + } +} - mMap = map; +void Being::removeGuild(int id) +{ + if (this == player_node && socialWindow) + { + socialWindow->removeTab(mGuilds[id]); + } - // Add sprite to potential new map - if (mMap) - mMapSprite = mMap->addSprite(this); + mGuilds[id]->removeMember(mId); + mGuilds.erase(id); +} + +Guild *Being::getGuild(const std::string &guildName) const +{ + std::map<int, Guild*>::const_iterator itr, itr_end = mGuilds.end(); + for (itr = mGuilds.begin(); itr != itr_end; ++itr) + { + Guild *guild = itr->second; + if (guild->getName() == guildName) + { + return guild; + } + } - // Clear particle effect list because child particles became invalid - mChildParticleEffects.clear(); - mMustResetParticles = true; // Reset status particles on next redraw + return NULL; } -void Being::controlParticle(Particle *particle) +Guild *Being::getGuild(int id) const { - mChildParticleEffects.addLocally(particle); + std::map<int, Guild*>::const_iterator itr; + itr = mGuilds.find(id); + if (itr != mGuilds.end()) + { + return itr->second; + } + + return NULL; +} + +void Being::clearGuilds() +{ + std::map<int, Guild*>::const_iterator itr, itr_end = mGuilds.end(); + for (itr = mGuilds.begin(); itr != itr_end; ++itr) + { + Guild *guild = itr->second; + + if (this == player_node && socialWindow) + socialWindow->removeTab(guild); + + guild->removeMember(mId); + } + + mGuilds.clear(); +} + +void Being::setParty(Party *party) +{ + if (party == mParty) + return; + + Party *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::fireMissile(Being *victim, const std::string &particle) @@ -416,13 +562,37 @@ void Being::setAction(Action action, int attackType) break; case ATTACK: if (mEquippedWeapon) + { currentAction = mEquippedWeapon->getAttackType(); + reset(); + } else - currentAction = ACTION_ATTACK; + { + mAttackType = attackType; + currentAction = mInfo->getAttack(attackType)->action; + reset(); + + int rotation = 0; + //attack particle effect + std::string particleEffect = mInfo->getAttack(attackType) + ->particleEffect; + if (!particleEffect.empty() && Particle::enabled) + { + switch (mSpriteDirection) + { + case DIRECTION_DOWN: rotation = 0; break; + case DIRECTION_LEFT: rotation = 90; break; + case DIRECTION_UP: rotation = 180; break; + case DIRECTION_RIGHT: rotation = 270; break; + default: break; + } + Particle *p; + p = particleEngine->addEffect(particleEffect, 0, 0, + rotation); + controlParticle(p); + } + } - for (SpriteIterator it = mSprites.begin(); it != mSprites.end(); it++) - if (*it) - (*it)->reset(); break; case HURT: //currentAction = ACTION_HURT; // Buggy: makes the player stop @@ -431,6 +601,7 @@ void Being::setAction(Action action, int attackType) break; case DEAD: currentAction = ACTION_DEAD; + sound.playSfx(mInfo->getSound(SOUND_EVENT_DIE)); break; case STAND: currentAction = ACTION_STAND; @@ -439,9 +610,7 @@ void Being::setAction(Action action, int attackType) if (currentAction != ACTION_INVALID) { - for (SpriteIterator it = mSprites.begin(); it != mSprites.end(); it++) - if (*it) - (*it)->play(currentAction); + play(currentAction); mAction = action; } } @@ -469,9 +638,7 @@ void Being::setDirection(Uint8 direction) dir = DIRECTION_LEFT; mSpriteDirection = dir; - for (SpriteIterator it = mSprites.begin(); it != mSprites.end(); it++) - if (*it) - (*it)->setDirection(dir); + CompoundSprite::setDirection(dir); } /** TODO: Used by eAthena only */ @@ -607,6 +774,70 @@ void Being::logic() } else if (Net::getNetworkType() == ServerInfo::TMWATHENA) { + if (getType() == MONSTER && (mAction != STAND)) + { + mFrame = (int) ((get_elapsed_time(mWalkTime) * 4) / getWalkSpeed().x); + + if (mFrame >= 4 && mAction != DEAD) + nextTile(); + } + else if (getType() == PLAYER) + { + switch (mAction) + { + case STAND: + case SIT: + case DEAD: + case HURT: + break; + + case WALK: + mFrame = (int) ((get_elapsed_time(mWalkTime) * 6) + / getWalkSpeed().x); + if (mFrame >= 6) + nextTile(); + break; + + case ATTACK: + int rotation = 0; + std::string particleEffect = ""; + int frames = 4; + + if (mEquippedWeapon && + mEquippedWeapon->getAttackType() == ACTION_ATTACK_BOW) + { + frames = 5; + } + + mFrame = (get_elapsed_time(mWalkTime) * frames) / mAttackSpeed; + + //attack particle effect + if (mEquippedWeapon) + particleEffect = mEquippedWeapon->getParticleEffect(); + + if (!particleEffect.empty() && Particle::enabled && mFrame == 1) + { + switch (mDirection) + { + case DOWN: rotation = 0; break; + case LEFT: rotation = 90; break; + case UP: rotation = 180; break; + case RIGHT: rotation = 270; break; + default: break; + } + Particle *p; + p = particleEngine->addEffect("graphics/particles/" + + particleEffect, 0, 0, rotation); + controlParticle(p); + } + + if (mFrame >= frames) + nextTile(); + + break; + } + } + // Update pixel coordinates setPosition(mX * 32 + 16 + getXOffset(), mY * 32 + 32 + getYOffset()); @@ -619,69 +850,7 @@ void Being::logic() mEmotion = 0; } - // Update sprite animations - if (mUsedTargetCursor) - mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK); - - for (SpriteIterator it = mSprites.begin(); it != mSprites.end(); it++) - if (*it) - (*it)->update(tick_time * MILLISECONDS_IN_A_TICK); - - // Restart status/particle effects, if needed - if (mMustResetParticles) - { - mMustResetParticles = false; - for (std::set<int>::iterator it = mStatusEffects.begin(); - it != mStatusEffects.end(); it++) - { - const StatusEffect *effect = StatusEffect::getStatusEffect(*it, true); - if (effect && effect->particleEffectIsPersistent()) - updateStatusEffect(*it, true); - } - } - - // Update particle effects - mChildParticleEffects.moveTo(mPos.x, mPos.y); -} - -void Being::draw(Graphics *graphics, int offsetX, int offsetY) const -{ - // TODO: Eventually, we probably should fix all sprite offsets so that - // these translations aren't necessary anymore. The sprites know - // best where their base point should be. - const int px = getPixelX() + offsetX - 16; - // Temporary fix to the Y offset. - const int py = getPixelY() + offsetY - - ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32); - - if (mUsedTargetCursor) - mUsedTargetCursor->draw(graphics, px, py); - - for (SpriteConstIterator it = mSprites.begin(); it != mSprites.end(); it++) - { - if (*it) - { - if ((*it)->getAlpha() != mAlpha) - (*it)->setAlpha(mAlpha); - (*it)->draw(graphics, px, py); - } - } -} - -void Being::drawSpriteAt(Graphics *graphics, int x, int y) const -{ - const int px = x - 16; - const int py = y - 32; - - for (SpriteConstIterator it = mSprites.begin(); it != mSprites.end(); it++) - { - if (*it) - { - if ((*it)->getAlpha() != mAlpha) - (*it)->setAlpha(mAlpha); - (*it)->draw(graphics, px, py); - } - } + ActorSprite::logic(); } void Being::drawEmotion(Graphics *graphics, int offsetX, int offsetY) @@ -751,67 +920,6 @@ void Being::drawSpeech(int offsetX, int offsetY) } } -void Being::setStatusEffectBlock(int offset, Uint16 newEffects) -{ - for (int i = 0; i < STATUS_EFFECTS; i++) - { - int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i); - - if (index != -1) - setStatusEffect(index, (newEffects & (1 << i)) > 0); - } -} - -void Being::handleStatusEffect(StatusEffect *effect, int effectId) -{ - if (!effect) - return; - - // TODO: Find out how this is meant to be used - // (SpriteAction != Being::Action) - //SpriteAction action = effect->getAction(); - //if (action != ACTION_INVALID) - // setAction(action); - - Particle *particle = effect->getParticle(); - - if (effectId >= 0) - { - mStatusParticleEffects.setLocally(effectId, particle); - } - else - { - mStunParticleEffects.clearLocally(); - if (particle) - mStunParticleEffects.addLocally(particle); - } -} - -void Being::updateStunMode(int oldMode, int newMode) -{ - handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1); - handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1); -} - -void Being::updateStatusEffect(int index, bool newStatus) -{ - handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index); -} - -void Being::setStatusEffect(int index, bool active) -{ - const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end(); - - if (active != wasActive) - { - updateStatusEffect(index, active); - if (active) - mStatusEffects.insert(index); - else - mStatusEffects.erase(index); - } -} - /** TODO: eAthena only */ int Being::getOffset(char pos, char neg) const { @@ -844,176 +952,175 @@ int Being::getOffset(char pos, char neg) const int Being::getWidth() const { - AnimatedSprite *base = NULL; - - for (SpriteConstIterator it = mSprites.begin(); it != mSprites.end(); it++) - if ((base = (*it))) - break; - - if (base) - return std::max(base->getWidth(), DEFAULT_BEING_WIDTH); - - return DEFAULT_BEING_WIDTH; + return std::max(CompoundSprite::getWidth(), DEFAULT_BEING_WIDTH); } int Being::getHeight() const { - AnimatedSprite *base = NULL; - - for (SpriteConstIterator it = mSprites.begin(); it != mSprites.end(); it++) - if ((base = (*it))) - break; + return std::max(CompoundSprite::getHeight(), DEFAULT_BEING_HEIGHT); +} - if (base) - return std::max(base->getHeight(), DEFAULT_BEING_HEIGHT); +void Being::updateCoords() +{ + if (!mDispName) + return; - return DEFAULT_BEING_HEIGHT; + // Monster names show above the sprite instead of below it + if (getType() == MONSTER) + mDispName->adviseXY(getPixelX(), + getPixelY() - getHeight() - mDispName->getHeight()); + else + mDispName->adviseXY(getPixelX(), getPixelY()); } -void Being::setTargetAnimation(SimpleAnimation *animation) +void Being::optionChanged(const std::string &value) { - mUsedTargetCursor = animation; - mUsedTargetCursor->reset(); + if (getType() == PLAYER && value == "visiblenames") + { + setShowName(config.getValue("visiblenames", 1)); + } } -struct EffectDescription { - std::string mGFXEffect; - std::string mSFXEffect; -}; - -static EffectDescription *default_effect = NULL; -static std::map<int, EffectDescription *> effects; -static bool effects_initialized = false; - -static EffectDescription *getEffectDescription(xmlNodePtr node, int *id) +void Being::flashName(int time) { - EffectDescription *ed = new EffectDescription; - - *id = atoi(XML::getProperty(node, "id", "-1").c_str()); - ed->mSFXEffect = XML::getProperty(node, "audio", ""); - ed->mGFXEffect = XML::getProperty(node, "particle", ""); - - return ed; + if (mDispName) + mDispName->flash(time); } -static EffectDescription *getEffectDescription(int effectId) +void Being::showName() { - if (!effects_initialized) - { - XML::Document doc(BEING_EFFECTS_FILE); - xmlNodePtr root = doc.rootNode(); + delete mDispName; + mDispName = 0; + std::string mDisplayName(mName); - if (!root || !xmlStrEqual(root->name, BAD_CAST "being-effects")) - { - logger->log("Error loading being effects file: " - BEING_EFFECTS_FILE); - return NULL; - } + if (config.getValue("showgender", false)) + { + if (getGender() == GENDER_FEMALE) + mDisplayName += " \u2640"; + else if (getGender() == GENDER_MALE) + mDisplayName += " \u2642"; + } - for_each_xml_child_node(node, root) + if (getType() == MONSTER) + { + if (config.getValue("showMonstersTakedDamage", false)) { - int id; - - if (xmlStrEqual(node->name, BAD_CAST "effect")) - { - EffectDescription *EffectDescription = - getEffectDescription(node, &id); - effects[id] = EffectDescription; - } - else if (xmlStrEqual(node->name, BAD_CAST "default")) - { - EffectDescription *effectDescription = - getEffectDescription(node, &id); - - if (default_effect) - delete default_effect; - - default_effect = effectDescription; - } + mDisplayName += ", " + toString(getDamageTaken()); } + } - effects_initialized = true; - } // done initializing - - EffectDescription *ed = effects[effectId]; + mDispName = new FlashText(mDisplayName, getPixelX(), getPixelY(), + gcn::Graphics::CENTER, mNameColor); - return ed ? ed : default_effect; + updateCoords(); } -void Being::internalTriggerEffect(int effectId, bool sfx, bool gfx) +void Being::updateColors() { - logger->log("Special effect #%d on %s", effectId, - getId() == player_node->getId() ? "self" : "other"); - - EffectDescription *ed = getEffectDescription(effectId); - - if (!ed) + if (getType() == MONSTER) { - logger->log("Unknown special effect and no default recorded"); - return; + mNameColor = &userPalette->getColor(UserPalette::MONSTER); + mTextColor = &userPalette->getColor(UserPalette::MONSTER); } - - if (gfx && !ed->mGFXEffect.empty()) + else if (getType() == NPC) { - Particle *selfFX; - - selfFX = particleEngine->addEffect(ed->mGFXEffect, 0, 0); - controlParticle(selfFX); + 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 = &userPalette->getColor(Theme::PLAYER); - if (sfx && !ed->mSFXEffect.empty()) - sound.playSfx(ed->mSFXEffect); -} + if (mIsGM) + { + mTextColor = &userPalette->getColor(UserPalette::GM); + mNameColor = &userPalette->getColor(UserPalette::GM); + } + else if (mParty && mParty == player_node->getParty()) + { + mNameColor = &userPalette->getColor(UserPalette::PARTY); + } + else + { + mNameColor = &userPalette->getColor(UserPalette::PC); + } + } -void Being::updateCoords() -{ if (mDispName) { - mDispName->adviseXY(getPixelX(), getPixelY()); + mDispName->setColor(mNameColor); } } -void Being::flashName(int time) +void Being::setSprite(unsigned int slot, int id, const std::string &color, + bool isWeapon) { - if (mDispName) - mDispName->flash(time); -} + assert(slot < Net::getCharHandler()->maxSprite()); -void Being::showName() -{ - delete mDispName; - mDispName = 0; - std::string mDisplayName(mName); + if (slot >= size()) + ensureSize(slot + 1); - if (getType() == PLAYER) + if (slot >= mSpriteIDs.size()) + mSpriteIDs.resize(slot + 1, 0); + + if (slot >= mSpriteColors.size()) + mSpriteColors.resize(slot + 1, ""); + + // id = 0 means unequip + if (id == 0) { - if (config.getValue("showgender", false)) - { - Player* player = static_cast<Player*>(this); - if (player) - { - if (player->getGender() == GENDER_FEMALE) - mDisplayName += " \u2640"; - else - mDisplayName += " \u2642"; - } - } + removeSprite(slot); + + if (isWeapon) + mEquippedWeapon = NULL; } - else if (getType() == MONSTER) + else { - if (config.getValue("showMonstersTakedDamage", false)) + std::string filename = ItemDB::get(id).getSprite(mGender); + AnimatedSprite *equipmentSprite = NULL; + + if (!filename.empty()) { - mDisplayName += ", " + toString(getDamageTaken()); + if (!color.empty()) + filename += "|" + color; + + equipmentSprite = AnimatedSprite::load("graphics/sprites/" + + filename); } + + if (equipmentSprite) + equipmentSprite->setDirection(getSpriteDirection()); + + CompoundSprite::setSprite(slot, equipmentSprite); + + if (isWeapon) + mEquippedWeapon = &ItemDB::get(id); + + setAction(mAction); } - mDispName = new FlashText(mDisplayName, getPixelX(), getPixelY(), - gcn::Graphics::CENTER, mNameColor); + mSpriteIDs[slot] = id; + mSpriteColors[slot] = color; +} + +void Being::setSpriteID(unsigned int slot, int id) +{ + setSprite(slot, id, mSpriteColors[slot]); +} + +void Being::setSpriteColor(unsigned int slot, const std::string &color) +{ + setSprite(slot, mSpriteIDs[slot], color); } int Being::getNumberOfLayers() const { - return mSprites.size(); + return CompoundSprite::getNumberOfLayers(); } void Being::load() @@ -1033,3 +1140,44 @@ void Being::updateName() if (mShowName) showName(); } + +void Being::setGender(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(bool gm) +{ + mIsGM = gm; + + updateColors(); +} + +bool Being::canTalk() +{ + return mType == NPC; +} + +void Being::talkTo() +{ + Net::getNpcHandler()->talk(mId); +} + +bool Being::isTalking() +{ + return NpcDialog::isActive() || BuyDialog::isActive() || + SellDialog::isActive() || BuySellDialog::isActive() || + NpcPostDialog::isActive(); +} |