summaryrefslogtreecommitdiff
path: root/src/being.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/being.cpp')
-rw-r--r--src/being.cpp696
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;
+}