diff options
Diffstat (limited to 'src/being.cpp')
-rw-r--r-- | src/being.cpp | 696 |
1 files changed, 518 insertions, 178 deletions
diff --git a/src/being.cpp b/src/being.cpp index 442c08ef..7f5a7d33 100644 --- a/src/being.cpp +++ b/src/being.cpp @@ -1,109 +1,160 @@ /* * The Mana World - * Copyright 2004 The Mana World Development Team + * Copyright (C) 2004 The Mana World Development Team * * This file is part of The Mana World. * - * The Mana World is free software; you can redistribute it and/or modify + * 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. * - * The Mana World is distributed in the hope that it will be useful, + * 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 The Mana World; if not, write to the Free Software + * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "being.h" -#include <cassert> -#include <cmath> - #include "animatedsprite.h" -#include "equipment.h" +#include "configuration.h" +#include "effectmanager.h" #include "game.h" #include "graphics.h" +#include "localplayer.h" #include "log.h" #include "map.h" #include "particle.h" +#include "simpleanimation.h" #include "sound.h" -#include "localplayer.h" +#include "text.h" +#include "statuseffect.h" +#include "gui/speechbubble.h" + +#include "resources/colordb.h" +#include "resources/emotedb.h" +#include "resources/image.h" #include "resources/itemdb.h" -#include "resources/resourcemanager.h" -#include "resources/imageset.h" #include "resources/iteminfo.h" +#include "resources/resourcemanager.h" #include "gui/gui.h" #include "gui/speechbubble.h" #include "utils/dtor.h" -#include "utils/tostring.h" +#include "utils/gettext.h" +#include "utils/stringutils.h" #include "utils/xml.h" +#include <cassert> +#include <cmath> + namespace { const bool debug_movement = true; } -#define HAIR_FILE "hair.xml" - -#include "utils/xml.h" #define BEING_EFFECTS_FILE "effects.xml" +#define HAIR_FILE "hair.xml" int Being::instances = 0; -ImageSet *Being::emotionSet = NULL; +int Being::mNumberOfHairstyles = 1; +std::vector<AnimatedSprite*> Being::emotionSet; + +static const int X_SPEECH_OFFSET = 18; +static const int Y_SPEECH_OFFSET = 60; + +static const int DEFAULT_WIDTH = 32; +static const int DEFAULT_HEIGHT = 32; Being::Being(int id, int job, Map *map): +#ifdef EATHENA_SUPPORT + mX(0), mY(0), + mWalkTime(0), +#endif mEmotion(0), mEmotionTime(0), mAttackSpeed(350), mAction(STAND), mJob(job), mId(id), - mSpriteDirection(DIRECTION_DOWN), mDirection(DOWN), + mDirection(DOWN), +#ifdef TMWSERV_SUPPORT + mSpriteDirection(DIRECTION_DOWN), +#endif mMap(NULL), + mName(""), + mIsGM(false), + mParticleEffects(config.getValue("particleeffects", 1)), mEquippedWeapon(NULL), +#ifdef TMWSERV_SUPPORT mHairStyle(0), +#else + mHairStyle(1), +#endif mHairColor(0), mGender(GENDER_UNSPECIFIED), mSpeechTime(0), + mPx(0), mPy(0), + mStunMode(0), mSprites(VECTOREND_SPRITE, NULL), mSpriteIDs(VECTOREND_SPRITE, 0), mSpriteColors(VECTOREND_SPRITE, ""), - mWalkSpeed(100) + mStatusParticleEffects(&mStunParticleEffects, false), + mChildParticleEffects(&mStatusParticleEffects, false), + mMustResetParticles(false), +#ifdef TMWSERV_SUPPORT + mWalkSpeed(100), +#else + mWalkSpeed(150), +#endif + mUsedTargetCursor(NULL) { setMap(map); - mSpeechBubble = new SpeechBubble(); + mSpeechBubble = new SpeechBubble; if (instances == 0) { - // Load the emotion set - ResourceManager *rm = ResourceManager::getInstance(); - emotionSet = rm->getImageSet("graphics/sprites/emotions.png", 30, 32); - if (!emotionSet) - logger->error("Unable to load emotions!"); + // Setup emote sprites + for (int i = 0; i <= EmoteDB::getLast(); i++) + { + EmoteInfo info = EmoteDB::get(i); + + std::string file = "graphics/sprites/" + info.sprites.front()->sprite; + int variant = info.sprites.front()->variant; + emotionSet.push_back(AnimatedSprite::load(file, variant)); + } + + // Hairstyles are encoded as negative numbers. Count how far negative + // we can go. + int hairstyles = 1; + while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE) != "error.xml") + { + hairstyles++; + } + mNumberOfHairstyles = hairstyles; } instances++; + mSpeech = ""; + mNameColor = 0x202020; + mText = 0; } Being::~Being() { + mUsedTargetCursor = NULL; delete_all(mSprites); clearPath(); - for ( std::list<Particle *>::iterator i = mChildParticleEffects.begin(); - i != mChildParticleEffects.end(); - i++) - { - (*i)->kill(); - } + if (player_node && player_node->getTarget() == this) + player_node->setTarget(NULL); setMap(NULL); @@ -111,11 +162,11 @@ Being::~Being() if (instances == 0) { - emotionSet->decRef(); - emotionSet = NULL; + delete_all(emotionSet); } delete mSpeechBubble; + delete mText; } void Being::setPosition(const Vector &pos) @@ -125,6 +176,15 @@ void Being::setPosition(const Vector &pos) mPath.clear(); } +#ifdef EATHENA_SUPPORT +void Being::setDestination(Uint16 destX, Uint16 destY) +{ + if (mMap) + setPath(mMap->findPath(mX, mY, destX, destY, getWalkMask())); +} +#endif + +#ifdef TMWSERV_SUPPORT void Being::adjustCourse(int srcX, int srcY, int dstX, int dstY) { if (debug_movement) @@ -289,6 +349,7 @@ void Being::setDestination(int destX, int destY) adjustCourse((int) mPos.x, (int) mPos.y, destX, destY); } +#endif // TMWSERV_SUPPORT void Being::clearPath() { @@ -297,14 +358,22 @@ void Being::clearPath() void Being::setPath(const Path &path) { - std::cout << this << " New path: " << path << std::endl; mPath = path; +#ifdef TMWSERV_SUPPORT + std::cout << this << " New path: " << path << std::endl; +#else + if (mAction != WALK && mAction != DEAD) + { + nextStep(); + mWalkTime = tick_time; + } +#endif } void Being::setHairStyle(int style, int color) { - mHairStyle = style < 0 ? mHairStyle : style % getHairStylesNr(); - mHairColor = color < 0 ? mHairColor : color % getHairColorsNr(); + mHairStyle = style < 0 ? mHairStyle : style % mNumberOfHairstyles; + mHairColor = color < 0 ? mHairColor : color % ColorDB::size(); } void Being::setSprite(int slot, int id, const std::string &color) @@ -317,7 +386,43 @@ void Being::setSprite(int slot, int id, const std::string &color) void Being::setSpeech(const std::string &text, Uint32 time) { mSpeech = text; - mSpeechTime = 500; + + // Trim whitespace + trim(mSpeech); + + // check for links + std::string::size_type start = mSpeech.find('['); + std::string::size_type end = mSpeech.find(']', start); + + while (start != std::string::npos && end != 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) < end)) + { + start = mSpeech.find('[', start + 1); + } + + std::string::size_type position = mSpeech.find('|'); + if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@') + { + mSpeech.erase(end, 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); + end = mSpeech.find(']', start); + } + + if (!mSpeech.empty()) + mSpeechTime = time <= SPEECH_MAX_TIME ? time : SPEECH_MAX_TIME; } void Being::takeDamage(int amount) @@ -327,69 +432,71 @@ void Being::takeDamage(int amount) // Selecting the right color if (damage == "miss") - { font = hitYellowFont; - } else { - // Hit particle effect - controlParticle(particleEngine->addEffect( - "graphics/particles/hit.particle.xml", mPos.x, mPos.y)); - if (getType() == MONSTER) - { font = hitBlueFont; - } else - { font = hitRedFont; - } } // Show damage number particleEngine->addTextSplashEffect(damage, 255, 255, 255, font, +#ifdef TMWSERV_SUPPORT (int) mPos.x + 16, (int) mPos.y + 16); +#else + mPx + 16, mPy + 16); +#endif + effectManager->trigger(26, this); } +void Being::showCrit() +{ + effectManager->trigger(28, this); + +} + +#ifdef TMWSERV_SUPPORT void Being::handleAttack() +#else +void Being::handleAttack(Being *victim, int damage) +#endif { setAction(Being::ATTACK); +#ifdef EATHENA_SUPPORT + mFrame = 0; + mWalkTime = tick_time; +#endif } void Being::setMap(Map *map) { // Remove sprite from potential previous map if (mMap) - { mMap->removeSprite(mSpriteIterator); - } mMap = map; // Add sprite to potential new map if (mMap) - { mSpriteIterator = mMap->addSprite(this); - } // Clear particle effect list because child particles became invalid mChildParticleEffects.clear(); + mMustResetParticles = true; // Reset status particles on next redraw } void Being::controlParticle(Particle *particle) { - if (particle) - { - // The effect may not die without the beings permission or we segfault - particle->disableAutoDelete(); - mChildParticleEffects.push_back(particle); - } + mChildParticleEffects.addLocally(particle); } void Being::setAction(Action action, int attackType) { SpriteAction currentAction = ACTION_INVALID; + switch (action) { case WALK: @@ -403,15 +510,14 @@ void Being::setAction(Action action, int attackType) { currentAction = mEquippedWeapon->getAttackType(); } - else { + else + { currentAction = ACTION_ATTACK; } for (int i = 0; i < VECTOREND_SPRITE; i++) { if (mSprites[i]) - { mSprites[i]->reset(); - } } break; case HURT: @@ -432,20 +538,18 @@ void Being::setAction(Action action, int attackType) for (int i = 0; i < VECTOREND_SPRITE; i++) { if (mSprites[i]) - { mSprites[i]->play(currentAction); - } } mAction = action; } } - void Being::setDirection(Uint8 direction) { if (mDirection == direction) return; +#ifdef TMWSERV_SUPPORT // if the direction does not change much, keep the common component int mFaceDirection = mDirection & direction; if (!mFaceDirection) @@ -462,16 +566,93 @@ void Being::setDirection(Uint8 direction) else dir = DIRECTION_LEFT; mSpriteDirection = dir; +#else + mDirection = direction; + SpriteDirection dir = getSpriteDirection(); +#endif for (int i = 0; i < VECTOREND_SPRITE; i++) { - if (mSprites[i] != NULL) - mSprites[i]->setDirection(dir); + if (mSprites[i]) + mSprites[i]->setDirection(dir); + } +} + +#ifdef EATHENA_SUPPORT +SpriteDirection Being::getSpriteDirection() const +{ + SpriteDirection dir; + + if (mDirection & UP) + { + dir = DIRECTION_UP; + } + else if (mDirection & DOWN) + { + dir = DIRECTION_DOWN; + } + else if (mDirection & RIGHT) + { + dir = DIRECTION_RIGHT; + } + else + { + dir = DIRECTION_LEFT; } + + return dir; } +void Being::nextStep() +{ + if (mPath.empty()) + { + setAction(STAND); + return; + } + + Position pos = mPath.front(); + mPath.pop_front(); + + int dir = 0; + if (pos.x > mX) + dir |= RIGHT; + else if (pos.x < mX) + dir |= LEFT; + if (pos.y > mY) + dir |= DOWN; + else if (pos.y < mY) + dir |= UP; + + setDirection(dir); + + if (!mMap->getWalk(pos.x, pos.y, getWalkMask())) + { + setAction(STAND); + return; + } + + mX = pos.x; + mY = pos.y; + setAction(WALK); + mWalkTime += mWalkSpeed / 10; +} +#endif + void Being::logic() { + // Reduce the time that speech is still displayed + if (mSpeechTime > 0) + mSpeechTime--; + + // Remove text if speech boxes aren't being used + if (mSpeechTime == 0 && mText) + { + delete mText; + mText = 0; + } + +#ifdef TMWSERV_SUPPORT const Vector dest = (mPath.empty()) ? mDest : Vector(mPath.front().x * 32 + 16, mPath.front().y * 32 + 16); @@ -506,56 +687,83 @@ void Being::logic() } else if (mAction == WALK) { setAction(STAND); } +#else + int oldPx = mPx; + int oldPy = mPy; - // Reduce the time that speech is still displayed - if (mSpeechTime > 0) - mSpeechTime--; + // Update pixel coordinates + mPx = mX * 32 + getXOffset(); + mPy = mY * 32 + getYOffset(); + + if (mPx != oldPx || mPy != oldPy) + { + updateCoords(); + } +#endif if (mEmotion != 0) { mEmotionTime--; - if (mEmotionTime == 0) { + if (mEmotionTime == 0) mEmotion = 0; - } } // Update sprite animations + if (mUsedTargetCursor) + mUsedTargetCursor->update(tick_time * 10); + for (int i = 0; i < VECTOREND_SPRITE; i++) { - if (mSprites[i] != NULL) - { + if (mSprites[i]) mSprites[i]->update(tick_time * 10); - } } - // Update particle effects - for (std::list<Particle *>::iterator i = mChildParticleEffects.begin(); - i != mChildParticleEffects.end();) - { - (*i)->setPosition(mPos.x, mPos.y); - if ((*i)->isExtinct()) - { - (*i)->kill(); - i = mChildParticleEffects.erase(i); - } - else { - i++; + // 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 +#ifdef TMWSERV_SUPPORT + mChildParticleEffects.moveTo((float) mPx + 16.0f, + (float) mPy + 32.0f); +#else + mChildParticleEffects.moveTo(mPos.x, mPos.y); +#endif } void Being::draw(Graphics *graphics, int offsetX, int offsetY) const { +#ifdef TMWSERV_SUPPORT int px = (int) mPos.x + offsetX; int py = (int) mPos.y + offsetY; +#else + int px = mPx + offsetX; + int py = mPy + offsetY; +#endif + + if (mUsedTargetCursor) + { + mUsedTargetCursor->draw(graphics, px, py); + } for (int i = 0; i < VECTOREND_SPRITE; i++) { - if (mSprites[i] != NULL) + if (mSprites[i]) { +#ifdef TMWSERV_SUPPORT // TODO: Eventually, we probably should fix all sprite offsets so // that this translation isn't necessary anymore. mSprites[i]->draw(graphics, px - 16, py - 32); +#else + mSprites[i]->draw(graphics, px, py); +#endif } } } @@ -565,26 +773,69 @@ void Being::drawEmotion(Graphics *graphics, int offsetX, int offsetY) if (!mEmotion) return; +#ifdef TMWSERV_SUPPORT const int px = (int) mPos.x + offsetX + 3; const int py = (int) mPos.y + offsetY - 60; +#else + const int px = mPx + offsetX + 3; + const int py = mPy + offsetY - 60; +#endif const int emotionIndex = mEmotion - 1; - if (emotionIndex >= 0 && emotionIndex < (int) emotionSet->size()) - graphics->drawImage(emotionSet->get(emotionIndex), px, py); + if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast()) + emotionSet[emotionIndex]->draw(graphics, px, py); } -void Being::drawSpeech(Graphics *graphics, int offsetX, int offsetY) +void Being::drawSpeech(int offsetX, int offsetY) { +#ifdef TMWSERV_SUPPORT int px = (int) mPos.x + offsetX; int py = (int) mPos.y + offsetY; +#else + const int px = mPx + offsetX; + const int py = mPy + offsetY; +#endif + const int speech = (int) config.getValue("speech", NAME_IN_BUBBLE); // Draw speech above this being - if (mSpeechTime > 0) + if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE || + speech == NO_NAME_IN_BUBBLE)) { - mSpeechBubble->setPosition(px - 50, py - 80 - (mSpeechBubble->getNumRows()*14) ); - mSpeechBubble->setText( mSpeech ); + const bool showName = (speech == NAME_IN_BUBBLE); + + if (mText) + { + delete mText; + mText = 0; + } + + mSpeechBubble->setCaption(showName ? mName : "", mNameColor); + + // Not quite centered, but close enough. However, it's not too important + // to get it right right now, as it doesn't take bubble collision into + // account yet. + mSpeechBubble->setText(mSpeech, showName); + mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() * 4 / 11), + py - 40 - (mSpeechBubble->getHeight())); mSpeechBubble->setVisible(true); } + else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD) + { + mSpeechBubble->setVisible(false); + // don't introduce a memory leak + if (mText) + delete mText; + + mText = new Text(mSpeech, mPx + X_SPEECH_OFFSET, mPy - Y_SPEECH_OFFSET, + gcn::Graphics::CENTER, gcn::Color(255, 255, 255)); + } + else if (speech == NO_SPEECH) + { + mSpeechBubble->setVisible(false); + if (mText) + delete mText; + mText = NULL; + } else if (mSpeechTime == 0) { mSpeechBubble->setVisible(false); @@ -596,115 +847,125 @@ Being::Type Being::getType() const return UNKNOWN; } -int Being::getWidth() const +void Being::setStatusEffectBlock(int offset, Uint16 newEffects) { - if (mSprites[BASE_SPRITE]) - { - return mSprites[BASE_SPRITE]->getWidth(); - } - else { - return Being::DEFAULT_WIDTH; - } -} + for (int i = 0; i < STATUS_EFFECTS; i++) { + int index = StatusEffect::blockEffectIndexToEffectIndex(offset + i); -int Being::getHeight() const -{ - if (mSprites[BASE_SPRITE]) - { - return mSprites[BASE_SPRITE]->getHeight(); - } - else { - return Being::DEFAULT_HEIGHT; + 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(); -static int hairStylesNr; -static int hairColorsNr; -static std::vector<std::string> hairColors; - -static void -initializeHair(void); + if (effectId >= 0) + mStatusParticleEffects.setLocally(effectId, particle); + else { + mStunParticleEffects.clearLocally(); + if (particle) + mStunParticleEffects.addLocally(particle); + } +} -int -Being::getHairStylesNr(void) +void Being::updateStunMode(int oldMode, int newMode) { - initializeHair(); - return hairStylesNr; + handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1); + handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1); } -int -Being::getHairColorsNr(void) +void Being::updateStatusEffect(int index, bool newStatus) { - initializeHair(); - return hairColorsNr; + handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index); } -std::string -Being::getHairColor(int index) +void Being::setStatusEffect(int index, bool active) { - initializeHair(); - if (index < 0 || index >= hairColorsNr) - return "#000000"; + const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end(); - return hairColors[index]; + if (active != wasActive) { + updateStatusEffect(index, active); + if (active) + mStatusEffects.insert(index); + else + mStatusEffects.erase(index); + } } -static bool hairInitialized = false; - -static void -initializeHair(void) +#ifdef EATHENA_SUPPORT +int Being::getOffset(char pos, char neg) const { - if (hairInitialized) - return; - - // Hairstyles are encoded as negative numbers. Count how far negative we can go. - int hairstylesCtr = -1; - while (ItemDB::get(hairstylesCtr).getSprite(GENDER_MALE) != "error.xml") - --hairstylesCtr; - - hairStylesNr = -hairstylesCtr; // done. - if (hairStylesNr == 0) - hairStylesNr = 1; // No hair style -> no hair + // Check whether we're walking in the requested direction + if (mAction != WALK || !(mDirection & (pos | neg))) + { + return 0; + } - hairColorsNr = 0; + int offset = (get_elapsed_time(mWalkTime) * 32) / mWalkSpeed; - XML::Document doc(HAIR_FILE); - xmlNodePtr root = doc.rootNode(); + // We calculate the offset _from_ the _target_ location + offset -= 32; + if (offset > 0) + { + offset = 0; + } - if (!root || !xmlStrEqual(root->name, BAD_CAST "colors")) + // Going into negative direction? Invert the offset. + if (mDirection & pos) { - logger->log("Error loading being hair configuration file"); - } else { - for_each_xml_child_node(node, root) - { - if (xmlStrEqual(node->name, BAD_CAST "color")) - { - int index = atoi(XML::getProperty(node, "id", "-1").c_str()); - std::string value = XML::getProperty(node, "value", ""); + offset = -offset; + } - if (index >= 0 && value != "") { - if (index >= hairColorsNr) { - hairColorsNr = index + 1; - hairColors.resize(hairColorsNr, "#000000"); - } - hairColors[index] = value; - } - } - } - } // done initializing + return offset; +} +#endif - if (hairColorsNr == 0) { // No colours -> black only - hairColorsNr = 1; - hairColors.resize(hairColorsNr, "#000000"); +int Being::getWidth() const +{ + if (mSprites[BASE_SPRITE]) + { + const int width = mSprites[BASE_SPRITE]->getWidth() > DEFAULT_WIDTH ? + mSprites[BASE_SPRITE]->getWidth() : + DEFAULT_WIDTH; + return width; + } + else + { + return DEFAULT_WIDTH; } - - hairInitialized = 1; } +int Being::getHeight() const +{ + if (mSprites[BASE_SPRITE]) + { + const int height = mSprites[BASE_SPRITE]->getHeight() > DEFAULT_HEIGHT ? + mSprites[BASE_SPRITE]->getHeight() : + DEFAULT_HEIGHT; + return height; + } + else + { + return DEFAULT_HEIGHT; + } +} +void Being::setTargetAnimation(SimpleAnimation* animation) +{ + mUsedTargetCursor = animation; + mUsedTargetCursor->reset(); +} struct EffectDescription { std::string mGFXEffect; @@ -715,8 +976,7 @@ static EffectDescription *default_effect = NULL; static std::map<int, EffectDescription *> effects; static bool effects_initialized = false; -static EffectDescription * -getEffectDescription(xmlNodePtr node, int *id) +static EffectDescription *getEffectDescription(xmlNodePtr node, int *id) { EffectDescription *ed = new EffectDescription; @@ -727,8 +987,7 @@ getEffectDescription(xmlNodePtr node, int *id) return ed; } -static EffectDescription * -getEffectDescription(int effectId) +static EffectDescription *getEffectDescription(int effectId) { if (!effects_initialized) { @@ -774,8 +1033,7 @@ getEffectDescription(int effectId) return ed; } -void -Being::internalTriggerEffect(int effectId, bool sfx, bool gfx) +void Being::internalTriggerEffect(int effectId, bool sfx, bool gfx) { logger->log("Special effect #%d on %s", effectId, getId() == player_node->getId() ? "self" : "other"); @@ -787,14 +1045,96 @@ Being::internalTriggerEffect(int effectId, bool sfx, bool gfx) return; } - if (gfx && ed->mGFXEffect != "") { + if (gfx && !ed->mGFXEffect.empty()) { Particle *selfFX; selfFX = particleEngine->addEffect(ed->mGFXEffect, 0, 0); controlParticle(selfFX); } - if (sfx && ed->mSFXEffect != "") { + if (sfx && !ed->mSFXEffect.empty()) { sound.playSfx(ed->mSFXEffect); } } + + + + +static int hairStylesNr; +static int hairColorsNr; +static std::vector<std::string> hairColors; + +static void initializeHair(); + +int Being::getHairStylesNr() +{ + initializeHair(); + return hairStylesNr; +} + +int Being::getHairColorsNr() +{ + initializeHair(); + return hairColorsNr; +} + +std::string Being::getHairColor(int index) +{ + initializeHair(); + if (index < 0 || index >= hairColorsNr) + return "#000000"; + + return hairColors[index]; +} + +static bool hairInitialized = false; + +static void initializeHair() +{ + if (hairInitialized) + return; + + // Hairstyles are encoded as negative numbers. Count how far negative we + // can go. + int hairstylesCtr = -1; + while (ItemDB::get(hairstylesCtr).getSprite(GENDER_MALE) != "error.xml") + --hairstylesCtr; + + hairStylesNr = -hairstylesCtr; // done. + if (hairStylesNr == 0) + hairStylesNr = 1; // No hair style -> no hair + + hairColorsNr = 0; + + XML::Document doc(HAIR_FILE); + xmlNodePtr root = doc.rootNode(); + + if (!root || !xmlStrEqual(root->name, BAD_CAST "colors")) + { + logger->log("Error loading being hair configuration file"); + } else { + for_each_xml_child_node(node, root) + { + if (xmlStrEqual(node->name, BAD_CAST "color")) + { + int index = atoi(XML::getProperty(node, "id", "-1").c_str()); + std::string value = XML::getProperty(node, "value", ""); + + if (index >= 0 && !value.empty()) { + if (index >= hairColorsNr) { + hairColorsNr = index + 1; + hairColors.resize(hairColorsNr, "#000000"); + } + hairColors[index] = value; + } + } + } + } // done initializing + + if (hairColorsNr == 0) { // No colors -> black only + hairColorsNr = 1; + hairColors.resize(hairColorsNr, "#000000"); + } + + hairInitialized = 1; +} |