summaryrefslogtreecommitdiff
path: root/src/being.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/being.cpp')
-rw-r--r--src/being.cpp866
1 files changed, 526 insertions, 340 deletions
diff --git a/src/being.cpp b/src/being.cpp
index d2dfc855..1b95b17d 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -21,104 +21,163 @@
#include "being.h"
+#include "actorspritemanager.h"
#include "animatedsprite.h"
#include "client.h"
#include "configuration.h"
#include "effectmanager.h"
+#include "event.h"
#include "graphics.h"
+#include "guild.h"
#include "localplayer.h"
#include "log.h"
#include "map.h"
#include "particle.h"
+#include "party.h"
+#include "playerrelations.h"
#include "simpleanimation.h"
#include "sound.h"
+#include "sprite.h"
#include "text.h"
#include "statuseffect.h"
#include "gui/gui.h"
+#include "gui/socialwindow.h"
#include "gui/speechbubble.h"
-#include "gui/theme.h"
-#include "gui/userpalette.h"
+#include "net/charhandler.h"
+#include "net/gamehandler.h"
+#include "net/net.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 "resources/theme.h"
+#include "resources/userpalette.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):
- mFrame(0),
- mWalkTime(0),
+Being::Being(int id, Type type, int subtype, Map *map):
+ ActorSprite(id),
+ mInfo(BeingInfo::Unknown),
+ mActionTime(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();
+
+ if (getType() == PLAYER)
+ mShowName = config.getBoolValue("visiblenames");
+
+ config.addListener("visiblenames", this);
+
+ if (getType() == PLAYER || getType() == NPC)
+ setShowName(true);
+
+ updateColors();
+ listen("Chat");
}
Being::~Being()
{
- mUsedTargetCursor = NULL;
- delete_all(mSprites);
-
- if (player_node && player_node->getTarget() == this)
- player_node->setTarget(NULL);
-
- setMap(NULL);
+ config.removeListener("visiblenames", this);
delete mSpeechBubble;
delete mDispName;
delete mText;
+ mSpeechBubble = 0;
+ mDispName = 0;
+ mText = 0;
+}
+
+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();
@@ -152,8 +211,9 @@ void Being::setDestination(int dstX, int dstY)
Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
dstX, dstY);
- Path thisPath = mMap->findPixelPath(mPos.x, mPos.y, dest.x, dest.y,
- getCollisionRadius(), getWalkMask());
+ Path thisPath = mMap->findPixelPath((int) mPos.x, (int) mPos.y,
+ dest.x, dest.y,
+ getCollisionRadius(), getWalkMask());
if (thisPath.empty())
{
@@ -186,10 +246,10 @@ void Being::setPath(const Path &path)
mPath = path;
if ((Net::getNetworkType() == ServerInfo::TMWATHENA) &&
- mAction != WALK && mAction != DEAD)
+ mAction != MOVE && mAction != DEAD)
{
nextTile();
- mWalkTime = tick_time;
+ mActionTime = tick_time;
}
}
@@ -235,7 +295,7 @@ void Being::setSpeech(const std::string &text, int time)
if (!mSpeech.empty())
mSpeechTime = time <= SPEECH_MAX_TIME ? time : SPEECH_MAX_TIME;
- const int speech = (int) config.getValue("speech", TEXT_OVERHEAD);
+ const int speech = config.getIntValue("speech");
if (speech == TEXT_OVERHEAD)
{
if (mText)
@@ -292,6 +352,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;
@@ -310,42 +372,50 @@ void Being::handleAttack(Being *victim, int damage, AttackType type)
if (this != player_node)
setAction(Being::ATTACK, 1);
- if (getType() == PLAYER && victim)
- {
- if (mEquippedWeapon)
- {
- fireMissile(victim, mEquippedWeapon->getMissileParticle());
- }
- }
+ if (getType() == PLAYER && victim && mEquippedWeapon)
+ fireMissile(victim, mEquippedWeapon->getMissileParticle());
+ else
+ fireMissile(victim, mInfo->getAttack(mAttackType)->missileParticle);
+
if (Net::getNetworkType() == ServerInfo::TMWATHENA)
{
- mFrame = 0;
- mWalkTime = tick_time;
+ reset();
+ mActionTime = 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 +430,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);
- mMap = map;
+ if (this == player_node && socialWindow)
+ {
+ socialWindow->addTab(guild);
+ }
+}
- // Add sprite to potential new map
- if (mMap)
- mMapSprite = mMap->addSprite(this);
+void Being::removeGuild(int id)
+{
+ if (this == player_node && socialWindow)
+ {
+ socialWindow->removeTab(mGuilds[id]);
+ }
+
+ mGuilds[id]->removeMember(mId);
+ mGuilds.erase(id);
+}
- // Clear particle effect list because child particles became invalid
- mChildParticleEffects.clear();
- mMustResetParticles = true; // Reset status particles on next redraw
+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;
+ }
+ }
+
+ 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)
@@ -404,46 +547,79 @@ void Being::fireMissile(Being *victim, const std::string &particle)
void Being::setAction(Action action, int attackType)
{
- SpriteAction currentAction = ACTION_INVALID;
+ std::string currentAction = SpriteAction::INVALID;
switch (action)
{
- case WALK:
- currentAction = ACTION_WALK;
+ case MOVE:
+ currentAction = SpriteAction::MOVE;
+ // Note: When adding a run action,
+ // Differentiate walk and run with action name,
+ // while using only the ACTION_MOVE.
break;
case SIT:
- currentAction = ACTION_SIT;
+ currentAction = SpriteAction::SIT;
break;
case ATTACK:
if (mEquippedWeapon)
- currentAction = mEquippedWeapon->getAttackType();
+ {
+ currentAction = mEquippedWeapon->getAttackAction();
+ reset();
+ }
else
- currentAction = ACTION_ATTACK;
+ {
+ mAttackType = attackType;
+ currentAction = mInfo->getAttack(attackType)->action;
+ reset();
+
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ 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
+ //currentAction = SpriteAction::HURT;// Buggy: makes the player stop
// attacking and unable to attack
- // again until he moves
+ // again until he moves.
+ // TODO: fix this!
break;
case DEAD:
- currentAction = ACTION_DEAD;
+ currentAction = SpriteAction::DEAD;
+ sound.playSfx(mInfo->getSound(SOUND_EVENT_DIE));
break;
case STAND:
- currentAction = ACTION_STAND;
+ currentAction = SpriteAction::STAND;
break;
}
- if (currentAction != ACTION_INVALID)
+ if (currentAction != SpriteAction::INVALID)
{
- for (SpriteIterator it = mSprites.begin(); it != mSprites.end(); it++)
- if (*it)
- (*it)->play(currentAction);
+ play(currentAction);
mAction = action;
}
+
+ if (currentAction != SpriteAction::MOVE)
+ mActionTime = tick_time;
}
void Being::setDirection(Uint8 direction)
@@ -469,9 +645,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 */
@@ -506,8 +680,8 @@ void Being::nextTile()
mX = pos.x;
mY = pos.y;
- setAction(WALK);
- mWalkTime += (int)(mWalkSpeed.x / 10);
+ setAction(MOVE);
+ mActionTime += (int)(mWalkSpeed.x / 10);
}
int Being::getCollisionRadius() const
@@ -570,8 +744,8 @@ void Being::logic()
else
setPosition(mPos + diff);
- if (mAction != WALK)
- setAction(WALK);
+ if (mAction != MOVE)
+ setAction(MOVE);
// Update the player sprite direction.
// N.B.: We only change this if the distance is more than one pixel.
@@ -600,13 +774,76 @@ void Being::logic()
// remove it and go to the next one.
mPath.pop_front();
}
- else if (mAction == WALK)
+ else if (mAction == MOVE)
{
setAction(STAND);
}
}
else if (Net::getNetworkType() == ServerInfo::TMWATHENA)
{
+ int frameCount = getFrameCount();
+
+ switch (mAction)
+ {
+ case STAND:
+ case SIT:
+ case DEAD:
+ case HURT:
+ break;
+
+ case MOVE:
+ if ((int) ((get_elapsed_time(mActionTime) * frameCount)
+ / getWalkSpeed().x) >= frameCount)
+ nextTile();
+ break;
+
+ case ATTACK:
+ int rotation = 0;
+ std::string particleEffect = "";
+
+ int curFrame = (get_elapsed_time(mActionTime) * frameCount)
+ / mAttackSpeed;
+
+ //attack particle effect
+ if (mEquippedWeapon)
+ {
+ particleEffect = mEquippedWeapon->getParticleEffect();
+
+ if (!particleEffect.empty() &&
+ findSameSubstring(particleEffect,
+ paths.getStringValue("particles")).empty())
+ particleEffect = paths.getStringValue("particles")
+ + particleEffect;
+ }
+ else
+ {
+ particleEffect = mInfo->getAttack(mAttackType)
+ ->particleEffect;
+ }
+
+ if (!particleEffect.empty() && Particle::enabled
+ && curFrame == 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(particleEffect, 0, 0,
+ rotation);
+ controlParticle(p);
+ }
+
+ if (curFrame >= frameCount)
+ nextTile();
+
+ break;
+ }
+
// Update pixel coordinates
setPosition(mX * 32 + 16 + getXOffset(),
mY * 32 + 32 + getYOffset());
@@ -619,68 +856,18 @@ 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);
- }
- }
-}
+ ActorSprite::logic();
-void Being::drawSpriteAt(Graphics *graphics, int x, int y) const
-{
- const int px = x - 16;
- const int py = y - 32;
+ int frameCount = getFrameCount();
+ if (frameCount < 10)
+ frameCount = 10;
- for (SpriteConstIterator it = mSprites.begin(); it != mSprites.end(); it++)
+ if (!isAlive() && Net::getGameHandler()->removeDeadBeings() &&
+ (int) ((get_elapsed_time(mActionTime)
+ / getWalkSpeed().x) >= frameCount))
{
- if (*it)
- {
- if ((*it)->getAlpha() != mAlpha)
- (*it)->setAlpha(mAlpha);
- (*it)->draw(graphics, px, py);
- }
+ if (getType() != PLAYER)
+ actorSpriteManager->destroy(this);
}
}
@@ -701,7 +888,7 @@ void Being::drawSpeech(int offsetX, int offsetY)
{
const int px = getPixelX() - offsetX;
const int py = getPixelY() - offsetY;
- const int speech = (int) config.getValue("speech", TEXT_OVERHEAD);
+ const int speech = config.getIntValue("speech");
// Draw speech above this being
if (mSpeechTime == 0)
@@ -751,72 +938,11 @@ 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
{
// Check whether we're walking in the requested direction
- if (mAction != WALK || !(mDirection & (pos | neg)))
+ if (mAction != MOVE || !(mDirection & (pos | neg)))
return 0;
int offset = 0;
@@ -824,9 +950,9 @@ int Being::getOffset(char pos, char neg) const
if (mMap)
{
offset = (pos == LEFT && neg == RIGHT) ?
- (int)((get_elapsed_time(mWalkTime)
+ (int)((get_elapsed_time(mActionTime)
* mMap->getTileWidth()) / mWalkSpeed.x) :
- (int)((get_elapsed_time(mWalkTime)
+ (int)((get_elapsed_time(mActionTime)
* mMap->getTileHeight()) / mWalkSpeed.y);
}
@@ -844,176 +970,182 @@ 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.getBoolValue("visiblenames"));
+ }
}
-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.getBoolValue("showgender"))
+ {
+ 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.getBoolValue("showMonstersTakedDamage"))
{
- 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
+ gcn::Font *font = 0;
+ if (player_node && player_node->getTarget() == this
+ && getType() != MONSTER)
+ {
+ font = boldFont;
+ }
- EffectDescription *ed = effects[effectId];
+ mDispName = new FlashText(mDisplayName, getPixelX(), getPixelY(),
+ gcn::Graphics::CENTER, mNameColor, font);
- 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(
+ paths.getStringValue("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()
@@ -1023,7 +1155,7 @@ void Being::load()
int hairstyles = 1;
while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE) !=
- paths.getValue("spriteErrorFile", "error.xml"))
+ paths.getStringValue("spriteErrorFile"))
hairstyles++;
mNumberOfHairstyles = hairstyles;
@@ -1034,3 +1166,57 @@ 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()
+{
+ Mana::Event event("doTalk");
+ event.setInt("npcId", mId);
+ event.trigger("NPC");
+}
+
+void Being::event(const std::string &channel, const Mana::Event &event)
+{
+ if (channel == "Chat" &&
+ (event.getName() == "Being" || event.getName() == "Player") &&
+ event.getInt("permissions") & PlayerRelation::SPEECH_FLOAT)
+ {
+ try
+ {
+ if (mId == event.getInt("beingId"))
+ {
+ setSpeech(event.getString("text"));
+ }
+ }
+ catch (Mana::BadEvent badEvent)
+ {}
+ }
+}