summaryrefslogtreecommitdiff
path: root/src/resources
diff options
context:
space:
mode:
Diffstat (limited to 'src/resources')
-rw-r--r--src/resources/action.cpp52
-rw-r--r--src/resources/action.h51
-rw-r--r--src/resources/ambientlayer.cpp126
-rw-r--r--src/resources/ambientlayer.h59
-rw-r--r--src/resources/ambientoverlay.cpp126
-rw-r--r--src/resources/ambientoverlay.h60
-rw-r--r--src/resources/animation.cpp46
-rw-r--r--src/resources/animation.h90
-rw-r--r--src/resources/beinginfo.cpp115
-rw-r--r--src/resources/beinginfo.h161
-rw-r--r--src/resources/colordb.cpp115
-rw-r--r--src/resources/colordb.h51
-rw-r--r--src/resources/dye.cpp320
-rw-r--r--src/resources/dye.h107
-rw-r--r--src/resources/emotedb.cpp222
-rw-r--r--src/resources/emotedb.h64
-rw-r--r--src/resources/image.cpp797
-rw-r--r--src/resources/image.h308
-rw-r--r--src/resources/imageloader.cpp114
-rw-r--r--src/resources/imageloader.h69
-rw-r--r--src/resources/imageset.cpp64
-rw-r--r--src/resources/imageset.h72
-rw-r--r--src/resources/imagewriter.cpp111
-rw-r--r--src/resources/imagewriter.h31
-rw-r--r--src/resources/itemdb.cpp463
-rw-r--r--src/resources/itemdb.h79
-rw-r--r--src/resources/iteminfo.cpp66
-rw-r--r--src/resources/iteminfo.h242
-rw-r--r--src/resources/mapreader.cpp723
-rw-r--r--src/resources/mapreader.h78
-rw-r--r--src/resources/monsterdb.cpp199
-rw-r--r--src/resources/monsterdb.h39
-rw-r--r--src/resources/music.cpp84
-rw-r--r--src/resources/music.h81
-rw-r--r--src/resources/npcdb.cpp127
-rw-r--r--src/resources/npcdb.h39
-rw-r--r--src/resources/resource.cpp58
-rw-r--r--src/resources/resource.h81
-rw-r--r--src/resources/resourcemanager.cpp658
-rw-r--r--src/resources/resourcemanager.h265
-rw-r--r--src/resources/soundeffect.cpp58
-rw-r--r--src/resources/soundeffect.h75
-rw-r--r--src/resources/specialdb.cpp132
-rw-r--r--src/resources/specialdb.h72
-rw-r--r--src/resources/spritedef.cpp339
-rw-r--r--src/resources/spritedef.h176
-rw-r--r--src/resources/wallpaper.cpp172
-rw-r--r--src/resources/wallpaper.h50
48 files changed, 7687 insertions, 0 deletions
diff --git a/src/resources/action.cpp b/src/resources/action.cpp
new file mode 100644
index 000000000..923aa72c9
--- /dev/null
+++ b/src/resources/action.cpp
@@ -0,0 +1,52 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/action.h"
+
+#include "resources/animation.h"
+
+#include "utils/dtor.h"
+
+Action::Action()
+{
+}
+
+Action::~Action()
+{
+ delete_all(mAnimations);
+}
+
+Animation *Action::getAnimation(int direction) const
+{
+ Animations::const_iterator i = mAnimations.find(direction);
+
+ // When the given direction is not available, return the first one.
+ // (either DEFAULT, or more usually DOWN).
+ if (i == mAnimations.end())
+ i = mAnimations.begin();
+
+ return (i == mAnimations.end()) ? NULL : i->second;
+}
+
+void Action::setAnimation(int direction, Animation *animation)
+{
+ mAnimations[direction] = animation;
+}
diff --git a/src/resources/action.h b/src/resources/action.h
new file mode 100644
index 000000000..3bd7b75aa
--- /dev/null
+++ b/src/resources/action.h
@@ -0,0 +1,51 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ACTION_H
+#define ACTION_H
+
+#include <libxml/tree.h>
+
+#include <map>
+
+class Animation;
+
+/**
+ * An action consists of several animations, one for each direction.
+ */
+class Action
+{
+ public:
+ Action();
+
+ ~Action();
+
+ void setAnimation(int direction, Animation *animation);
+
+ Animation *getAnimation(int direction) const;
+
+ protected:
+ typedef std::map<int, Animation*> Animations;
+ typedef Animations::iterator AnimationIterator;
+ Animations mAnimations;
+};
+
+#endif
diff --git a/src/resources/ambientlayer.cpp b/src/resources/ambientlayer.cpp
new file mode 100644
index 000000000..d80f380dd
--- /dev/null
+++ b/src/resources/ambientlayer.cpp
@@ -0,0 +1,126 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/ambientlayer.h"
+
+#include "graphics.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+AmbientLayer::AmbientLayer(Image *img, float parallax,
+ float speedX, float speedY, bool keepRatio):
+ mImage(img), mParallax(parallax),
+ mPosX(0), mPosY(0),
+ mSpeedX(speedX), mSpeedY(speedY),
+ mKeepRatio(keepRatio)
+{
+ if (!mImage)
+ return;
+
+ if (keepRatio && !mImage->useOpenGL()
+ /*&& defaultScreenWidth != 0
+ && defaultScreenHeight != 0*/
+ && graphics->getWidth() != defaultScreenWidth
+ && graphics->getHeight() != defaultScreenHeight)
+ {
+ // Rescale the overlay to keep the ratio as if we were on
+ // the default resolution...
+ Image *rescaledOverlay = mImage->SDLgetScaledImage(
+ static_cast<int>(mImage->getWidth()) / defaultScreenWidth
+ * graphics->getWidth(), static_cast<int>(mImage->getHeight())
+ / defaultScreenHeight * graphics->getHeight());
+
+ if (rescaledOverlay)
+ {
+ // Replace the resource with the new one...
+ std::string idPath = mImage->getIdPath() + "_rescaled";
+ ResourceManager::getInstance()->addResource(
+ idPath, rescaledOverlay);
+ mImage = rescaledOverlay;
+ rescaledOverlay->incRef();
+ }
+ else
+ {
+ mImage->incRef();
+ }
+ }
+ else
+ {
+ mImage->incRef();
+ }
+}
+
+AmbientLayer::~AmbientLayer()
+{
+ if (mImage)
+ mImage->decRef();
+}
+
+void AmbientLayer::update(int timePassed, float dx, float dy)
+{
+ if (!mImage)
+ return;
+
+ // Self scrolling of the overlay
+ mPosX -= mSpeedX * static_cast<float>(timePassed) / 10;
+ mPosY -= mSpeedY * static_cast<float>(timePassed) / 10;
+
+ // Parallax scrolling
+ mPosX += dx * mParallax;
+ mPosY += dy * mParallax;
+
+ int imgW = mImage->getWidth();
+ int imgH = mImage->getHeight();
+
+ // Wrap values
+ while (mPosX > imgW)
+ mPosX -= static_cast<float>(imgW);
+ while (mPosX < 0)
+ mPosX += static_cast<float>(imgW);
+
+ while (mPosY > imgH)
+ mPosY -= static_cast<float>(imgH);
+ while (mPosY < 0)
+ mPosY += static_cast<float>(imgH);
+}
+
+void AmbientLayer::draw(Graphics *graphics, int x, int y)
+{
+ if (!mImage)
+ return;
+
+ if (!mImage->useOpenGL() || !mKeepRatio)
+ {
+ graphics->drawImagePattern(mImage, static_cast<int>(-mPosX),
+ static_cast<int>(-mPosY), x + static_cast<int>(mPosX),
+ y + static_cast<int>(mPosY));
+ }
+ else
+ {
+ graphics->drawRescaledImagePattern(mImage, static_cast<int>(-mPosX),
+ static_cast<int>(-mPosY), x + static_cast<int>(mPosX),
+ y + static_cast<int>(mPosY),
+ static_cast<int>(mImage->getWidth())
+ / defaultScreenWidth * graphics->getWidth(),
+ static_cast<int>(mImage->getHeight()) / defaultScreenHeight
+ * graphics->getHeight());
+ }
+}
diff --git a/src/resources/ambientlayer.h b/src/resources/ambientlayer.h
new file mode 100644
index 000000000..928a568d2
--- /dev/null
+++ b/src/resources/ambientlayer.h
@@ -0,0 +1,59 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RESOURCES_AMBIENTOVERLAY_H
+#define RESOURCES_AMBIENTOVERLAY_H
+
+class Graphics;
+class Image;
+
+class AmbientLayer
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @param img the image this overlay displays
+ * @param parallax scroll factor based on camera position
+ * @param speedX scrolling speed in x-direction
+ * @param speedY scrolling speed in y-direction
+ * @param keepRatio rescale the image to keep
+ * the same ratio than in 800x600 resolution mode.
+ */
+ AmbientLayer(Image *img, float parallax,
+ float speedX, float speedY, bool keepRatio = false);
+
+ ~AmbientLayer();
+
+ void update(int timePassed, float dx, float dy);
+
+ void draw(Graphics *graphics, int x, int y);
+
+ private:
+ Image *mImage;
+ float mParallax;
+ float mPosX; /**< Current layer X position. */
+ float mPosY; /**< Current layer Y position. */
+ float mSpeedX; /**< Scrolling speed in X direction. */
+ float mSpeedY; /**< Scrolling speed in Y direction. */
+ bool mKeepRatio; /**< Keep overlay ratio on every resolution */
+};
+
+#endif
diff --git a/src/resources/ambientoverlay.cpp b/src/resources/ambientoverlay.cpp
new file mode 100644
index 000000000..8f579c8c7
--- /dev/null
+++ b/src/resources/ambientoverlay.cpp
@@ -0,0 +1,126 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/ambientoverlay.h"
+
+#include "graphics.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+AmbientOverlay::AmbientOverlay(Image *img, float parallax,
+ float speedX, float speedY, bool keepRatio):
+ mImage(img), mParallax(parallax),
+ mPosX(0), mPosY(0),
+ mSpeedX(speedX), mSpeedY(speedY),
+ mKeepRatio(keepRatio)
+{
+ if (!mImage)
+ return;
+
+ if (mImage && keepRatio && !mImage->useOpenGL()
+ /*&& defaultScreenWidth != 0
+ && defaultScreenHeight != 0*/
+ && graphics->getWidth() != defaultScreenWidth
+ && graphics->getHeight() != defaultScreenHeight)
+ {
+ // Rescale the overlay to keep the ratio as if we were on
+ // the default resolution...
+ Image *rescaledOverlay = mImage->SDLgetScaledImage(
+ static_cast<int>(mImage->getWidth()) / defaultScreenWidth
+ * graphics->getWidth(), static_cast<int>(mImage->getHeight())
+ / defaultScreenHeight * graphics->getHeight());
+
+ if (rescaledOverlay)
+ {
+ // Replace the resource with the new one...
+ std::string idPath = mImage->getIdPath() + "_rescaled";
+ ResourceManager::getInstance()->addResource(
+ idPath, rescaledOverlay);
+ mImage = rescaledOverlay;
+ }
+ else
+ {
+ mImage->incRef();
+ }
+ }
+ else
+ {
+ mImage->incRef();
+ }
+}
+
+AmbientOverlay::~AmbientOverlay()
+{
+ if (mImage)
+ mImage->decRef();
+}
+
+void AmbientOverlay::update(int timePassed, float dx, float dy)
+{
+ if (!mImage)
+ return;
+
+ // Self scrolling of the overlay
+ mPosX -= mSpeedX * static_cast<float>(timePassed) / 10;
+ mPosY -= mSpeedY * static_cast<float>(timePassed) / 10;
+
+ // Parallax scrolling
+ mPosX += dx * mParallax;
+ mPosY += dy * mParallax;
+
+ int imgW = mImage->getWidth();
+ int imgH = mImage->getHeight();
+
+ // Wrap values
+ while (mPosX > imgW)
+ mPosX -= static_cast<float>(imgW);
+ while (mPosX < 0)
+ mPosX += static_cast<float>(imgW);
+
+ while (mPosY > imgH)
+ mPosY -= static_cast<float>(imgH);
+ while (mPosY < 0)
+ mPosY += static_cast<float>(imgH);
+}
+
+void AmbientOverlay::draw(Graphics *graphics, int x, int y)
+{
+ if (!mImage)
+ return;
+
+ if (!mImage->useOpenGL() || !mKeepRatio)
+ {
+ graphics->drawImagePattern(mImage, static_cast<int>(-mPosX),
+ static_cast<int>(-mPosY), x + static_cast<int>(mPosX),
+ y + static_cast<int>(mPosY));
+ }
+ else
+ {
+ graphics->drawRescaledImagePattern(mImage, static_cast<int>(-mPosX),
+ static_cast<int>(-mPosY), x + static_cast<int>(mPosX),
+ y + static_cast<int>(mPosY),
+ static_cast<int>(mImage->getWidth()) / defaultScreenWidth
+ * graphics->getWidth(),
+ static_cast<int>(mImage->getHeight()) / defaultScreenHeight
+ * graphics->getHeight());
+ }
+}
diff --git a/src/resources/ambientoverlay.h b/src/resources/ambientoverlay.h
new file mode 100644
index 000000000..483ee98ed
--- /dev/null
+++ b/src/resources/ambientoverlay.h
@@ -0,0 +1,60 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RESOURCES_AMBIENTOVERLAY_H
+#define RESOURCES_AMBIENTOVERLAY_H
+
+class Graphics;
+class Image;
+
+class AmbientOverlay
+{
+ public:
+ /**
+ * Constructor.
+ *
+ * @param img the image this overlay displays
+ * @param parallax scroll factor based on camera position
+ * @param speedX scrolling speed in x-direction
+ * @param speedY scrolling speed in y-direction
+ * @param keepRatio rescale the image to keep
+ * the same ratio than in 800x600 resolution mode.
+ */
+ AmbientOverlay(Image *img, float parallax,
+ float speedX, float speedY, bool keepRatio = false);
+
+ ~AmbientOverlay();
+
+ void update(int timePassed, float dx, float dy);
+
+ void draw(Graphics *graphics, int x, int y);
+
+ private:
+ Image *mImage;
+ float mParallax;
+ float mPosX; /**< Current layer X position. */
+ float mPosY; /**< Current layer Y position. */
+ float mSpeedX; /**< Scrolling speed in X direction. */
+ float mSpeedY; /**< Scrolling speed in Y direction. */
+ bool mKeepRatio; /**< Keep overlay ratio on every resolution */
+};
+
+#endif
diff --git a/src/resources/animation.cpp b/src/resources/animation.cpp
new file mode 100644
index 000000000..f6ececba0
--- /dev/null
+++ b/src/resources/animation.cpp
@@ -0,0 +1,46 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/animation.h"
+
+#include "utils/dtor.h"
+
+Animation::Animation():
+ mDuration(0)
+{
+}
+
+void Animation::addFrame(Image *image, int delay, int offsetX, int offsetY)
+{
+ Frame frame = { image, delay, offsetX, offsetY };
+ mFrames.push_back(frame);
+ mDuration += delay;
+}
+
+void Animation::addTerminator()
+{
+ addFrame(NULL, 0, 0, 0);
+}
+
+bool Animation::isTerminator(const Frame &candidate)
+{
+ return (candidate.image == NULL);
+}
diff --git a/src/resources/animation.h b/src/resources/animation.h
new file mode 100644
index 000000000..e472adc40
--- /dev/null
+++ b/src/resources/animation.h
@@ -0,0 +1,90 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ANIMATION_H
+#define ANIMATION_H
+
+#include <libxml/tree.h>
+
+#include <vector>
+
+class Image;
+
+/**
+ * A single frame in an animation, with a delay and an offset.
+ */
+struct Frame
+{
+ Image *image;
+ int delay;
+ int offsetX;
+ int offsetY;
+};
+
+/**
+ * An animation consists of several frames, each with their own delay and
+ * offset.
+ */
+class Animation
+{
+ public:
+ Animation();
+
+ /**
+ * Appends a new animation at the end of the sequence.
+ */
+ void addFrame(Image *image, int delay, int offsetX, int offsetY);
+
+ /**
+ * Appends an animation terminator that states that the animation
+ * should not loop.
+ */
+ void addTerminator();
+
+ /**
+ * Returns the frame at the specified index.
+ */
+ Frame *getFrame(int index)
+ { return &(mFrames[index]); }
+
+ /**
+ * Returns the length of this animation in frames.
+ */
+ unsigned int getLength() const
+ { return static_cast<unsigned>(mFrames.size()); }
+
+ /**
+ * Returns the duration of this animation.
+ */
+ int getDuration() const
+ { return mDuration; }
+
+ /**
+ * Determines whether the given animation frame is a terminator.
+ */
+ static bool isTerminator(const Frame &phase);
+
+ protected:
+ std::vector<Frame> mFrames;
+ int mDuration;
+};
+
+#endif
diff --git a/src/resources/beinginfo.cpp b/src/resources/beinginfo.cpp
new file mode 100644
index 000000000..1b2ed3be1
--- /dev/null
+++ b/src/resources/beinginfo.cpp
@@ -0,0 +1,115 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/beinginfo.h"
+
+#include "log.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+BeingInfo *BeingInfo::Unknown = new BeingInfo;
+
+BeingInfo::BeingInfo():
+ mName(_("unnamed")),
+ mTargetCursorSize(ActorSprite::TC_MEDIUM),
+ mWalkMask(Map::BLOCKMASK_WALL | Map::BLOCKMASK_CHARACTER
+ | Map::BLOCKMASK_MONSTER),
+ mBlockType(Map::BLOCKTYPE_CHARACTER),
+ mTargetOffsetX(0), mTargetOffsetY(0),
+ mMaxHP(0), mStaticMaxHP(false)
+{
+ SpriteDisplay display;
+ display.sprites.push_back(SpriteReference::Empty);
+
+ setDisplay(display);
+}
+
+BeingInfo::~BeingInfo()
+{
+ delete_all(mSounds);
+ delete_all(mAttacks);
+ mSounds.clear();
+}
+
+void BeingInfo::setDisplay(SpriteDisplay display)
+{
+ mDisplay = display;
+}
+
+void BeingInfo::setTargetCursorSize(const std::string &size)
+{
+ if (size == "small")
+ {
+ setTargetCursorSize(ActorSprite::TC_SMALL);
+ }
+ else if (size == "medium")
+ {
+ setTargetCursorSize(ActorSprite::TC_MEDIUM);
+ }
+ else if (size == "large")
+ {
+ setTargetCursorSize(ActorSprite::TC_LARGE);
+ }
+ else
+ {
+ logger->log("Unknown target cursor type \"%s\" for %s - using medium "
+ "sized one", size.c_str(), getName().c_str());
+ setTargetCursorSize(ActorSprite::TC_MEDIUM);
+ }
+}
+
+void BeingInfo::addSound(SoundEvent event, const std::string &filename)
+{
+ if (mSounds.find(event) == mSounds.end())
+ mSounds[event] = new std::vector<std::string>;
+
+ if (mSounds[event])
+ mSounds[event]->push_back("sfx/" + filename);
+}
+
+const std::string &BeingInfo::getSound(SoundEvent event) const
+{
+ static std::string empty("");
+
+ SoundEvents::const_iterator i = mSounds.find(event);
+ return (i == mSounds.end()) ? empty :
+ i->second->at(rand() % i->second->size());
+}
+
+const Attack *BeingInfo::getAttack(int type) const
+{
+ // need remove in destructor?
+ static Attack *empty = new Attack(SpriteAction::ATTACK, "", "");
+
+ Attacks::const_iterator i = mAttacks.find(type);
+ return (i == mAttacks.end()) ? empty : (*i).second;
+}
+
+void BeingInfo::addAttack(int id, std::string action,
+ const std::string &particleEffect,
+ const std::string &missileParticle)
+{
+ if (mAttacks[id])
+ delete mAttacks[id];
+
+ mAttacks[id] = new Attack(action, particleEffect, missileParticle);
+}
diff --git a/src/resources/beinginfo.h b/src/resources/beinginfo.h
new file mode 100644
index 000000000..08fa1443e
--- /dev/null
+++ b/src/resources/beinginfo.h
@@ -0,0 +1,161 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEINGINFO_H
+#define BEINGINFO_H
+
+#include "actorsprite.h"
+
+#include "resources/spritedef.h"
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+struct Attack
+{
+ std::string action;
+ std::string particleEffect;
+ std::string missileParticle;
+
+ Attack(std::string action, std::string particleEffect,
+ std::string missileParticle)
+ {
+ this->action = action;
+ this->particleEffect = particleEffect;
+ this->missileParticle = missileParticle;
+ }
+};
+
+typedef std::map<int, Attack*> Attacks;
+
+enum SoundEvent
+{
+ SOUND_EVENT_HIT = 0,
+ SOUND_EVENT_MISS,
+ SOUND_EVENT_HURT,
+ SOUND_EVENT_DIE
+};
+
+typedef std::map<SoundEvent, std::vector<std::string>* > SoundEvents;
+
+/**
+ * Holds information about a certain type of monster. This includes the name
+ * of the monster, the sprite to display and the sounds the monster makes.
+ *
+ * @see MonsterDB
+ * @see NPCDB
+ */
+class BeingInfo
+{
+ public:
+ static BeingInfo *Unknown;
+
+ BeingInfo();
+
+ ~BeingInfo();
+
+ void setName(const std::string &name) { mName = name; }
+
+ const std::string &getName() const
+ { return mName; }
+
+ void setDisplay(SpriteDisplay display);
+
+ const SpriteDisplay &getDisplay() const
+ { return mDisplay; }
+
+ void setTargetCursorSize(const std::string &size);
+
+ void setTargetCursorSize(ActorSprite::TargetCursorSize targetSize)
+ { mTargetCursorSize = targetSize; }
+
+ ActorSprite::TargetCursorSize getTargetCursorSize() const
+ { return mTargetCursorSize; }
+
+ void addSound(SoundEvent event, const std::string &filename);
+
+ const std::string &getSound(SoundEvent event) const;
+
+ void addAttack(int id, std::string action,
+ const std::string &particleEffect,
+ const std::string &missileParticle);
+
+ const Attack *getAttack(int type) const;
+
+ void setWalkMask(unsigned char mask)
+ { mWalkMask = mask; }
+
+ /**
+ * Gets the way the being is blocked by other objects
+ */
+ unsigned char getWalkMask() const
+ { return mWalkMask; }
+
+ void setBlockType(Map::BlockType blockType)
+ { mBlockType = blockType; }
+
+ Map::BlockType getBlockType() const
+ { return mBlockType; }
+
+ void setTargetOffsetX(int n)
+ { mTargetOffsetX = n; }
+
+ int getTargetOffsetX() const
+ { return mTargetOffsetX; }
+
+ void setTargetOffsetY(int n)
+ { mTargetOffsetY = n; }
+
+ int getTargetOffsetY() const
+ { return mTargetOffsetY; }
+
+ void setMaxHP(int n)
+ { mMaxHP = n; }
+
+ int getMaxHP() const
+ { return mMaxHP; }
+
+ bool isStaticMaxHP() const
+ { return mStaticMaxHP; }
+
+ void setStaticMaxHP(bool n)
+ { mStaticMaxHP = n; }
+
+ private:
+ SpriteDisplay mDisplay;
+ std::string mName;
+ ActorSprite::TargetCursorSize mTargetCursorSize;
+ SoundEvents mSounds;
+ Attacks mAttacks;
+ unsigned char mWalkMask;
+ Map::BlockType mBlockType;
+ int mTargetOffsetX;
+ int mTargetOffsetY;
+ int mMaxHP;
+ bool mStaticMaxHP;
+};
+
+typedef std::map<int, BeingInfo*> BeingInfos;
+typedef BeingInfos::iterator BeingInfoIterator;
+
+#endif // BEINGINFO_H
diff --git a/src/resources/colordb.cpp b/src/resources/colordb.cpp
new file mode 100644
index 000000000..97842b02d
--- /dev/null
+++ b/src/resources/colordb.cpp
@@ -0,0 +1,115 @@
+/*
+ * Color database
+ * Copyright (C) 2008 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/colordb.h"
+
+#include "log.h"
+
+#include "utils/xml.h"
+
+#include <libxml/tree.h>
+
+namespace
+{
+ ColorDB::Colors mColors;
+ bool mLoaded = false;
+ std::string mFail = "#ffffff";
+}
+
+void ColorDB::load()
+{
+ if (mLoaded)
+ unload();
+
+ XML::Document *doc = new XML::Document("hair.xml");
+ xmlNodePtr root = doc->rootNode();
+ bool hairXml = true;
+
+ if (!root || !xmlStrEqual(root->name, BAD_CAST "colors"))
+ {
+ logger->log1("Trying to fall back on colors.xml");
+
+ hairXml = false;
+
+ delete doc;
+ doc = new XML::Document("colors.xml");
+ root = doc->rootNode();
+
+ if (!root || !xmlStrEqual(root->name, BAD_CAST "colors"))
+ {
+ logger->log1("ColorDB: Failed to find any color files.");
+ mColors[0] = mFail;
+ mLoaded = true;
+
+ delete doc;
+
+ return;
+ }
+ }
+ for_each_xml_child_node(node, root)
+ {
+ if (xmlStrEqual(node->name, BAD_CAST "color"))
+ {
+ int id = XML::getProperty(node, "id", 0);
+
+ if (mColors.find(id) != mColors.end())
+ logger->log("ColorDB: Redefinition of dye ID %d", id);
+
+ mColors[id] = hairXml ?
+ XML::getProperty(node, "value", "#FFFFFF") :
+ XML::getProperty(node, "dye", "#FFFFFF");
+ }
+ }
+
+ delete doc;
+
+ mLoaded = true;
+}
+
+void ColorDB::unload()
+{
+ logger->log1("Unloading color database...");
+
+ mColors.clear();
+ mLoaded = false;
+}
+
+std::string &ColorDB::get(int id)
+{
+ if (!mLoaded)
+ load();
+
+ ColorIterator i = mColors.find(id);
+
+ if (i == mColors.end())
+ {
+ logger->log("ColorDB: Error, unknown dye ID# %d", id);
+ return mFail;
+ }
+ else
+ {
+ return i->second;
+ }
+}
+
+int ColorDB::size()
+{
+ return static_cast<int>(mColors.size());
+}
diff --git a/src/resources/colordb.h b/src/resources/colordb.h
new file mode 100644
index 000000000..57b523882
--- /dev/null
+++ b/src/resources/colordb.h
@@ -0,0 +1,51 @@
+/*
+ * Color database
+ * Copyright (C) 2008 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COLOR_MANAGER_H
+#define COLOR_MANAGER_H
+
+#include <map>
+#include <string>
+
+/**
+ * Color information database.
+ */
+namespace ColorDB
+{
+ /**
+ * Loads the color data from <code>colors.xml</code>.
+ */
+ void load();
+
+ /**
+ * Clear the color data
+ */
+ void unload();
+
+ std::string &get(int id);
+
+ int size();
+
+ // Color DB
+ typedef std::map<int, std::string> Colors;
+ typedef Colors::iterator ColorIterator;
+}
+
+#endif
diff --git a/src/resources/dye.cpp b/src/resources/dye.cpp
new file mode 100644
index 000000000..6f9609453
--- /dev/null
+++ b/src/resources/dye.cpp
@@ -0,0 +1,320 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/dye.h"
+
+#include "log.h"
+
+#include <math.h>
+#include <sstream>
+
+DyePalette::DyePalette(const std::string &description)
+{
+ int size = static_cast<int>(description.length());
+ if (size == 0)
+ return;
+ if (description[0] != '#')
+ {
+ // TODO: load palette from file.
+ return;
+ }
+
+ int pos = 1;
+ for ( ; ; )
+ {
+ if (pos + 6 > size)
+ break;
+
+ int v = 0;
+ for (int i = 0; i < 6; ++i)
+ {
+ char c = description[pos + i];
+ int n;
+
+ if ('0' <= c && c <= '9')
+ n = c - '0';
+ else if ('A' <= c && c <= 'F')
+ n = c - 'A' + 10;
+ else if ('a' <= c && c <= 'f')
+ n = c - 'a' + 10;
+ else
+ goto error;
+
+ v = (v << 4) | n;
+ }
+ Color c =
+ {
+ {
+ static_cast<unsigned char>(v >> 16),
+ static_cast<unsigned char>(v >> 8),
+ static_cast<unsigned char>(v)
+ }
+ };
+ mColors.push_back(c);
+ pos += 6;
+
+ if (pos == size)
+ return;
+ if (description[pos] != ',')
+ break;
+
+ ++pos;
+ }
+
+ error:
+ logger->log("Error, invalid embedded palette: %s", description.c_str());
+}
+
+/*
+void DyePalette::addFirstColor(const int color[3])
+{
+ Color c = { {color[0], color[1], color[2]} };
+ mColors.insert(mColors.begin(), c);
+}
+
+void DyePalette::addLastColor(const int color[3])
+{
+ Color c = { {color[0], color[1], color[2]} };
+ mColors.push_back(c);
+}
+*/
+
+void DyePalette::getColor(int intensity, int color[3]) const
+{
+ if (intensity == 0)
+ {
+ color[0] = 0;
+ color[1] = 0;
+ color[2] = 0;
+ return;
+ }
+
+ int last = static_cast<int>(mColors.size());
+ if (last == 0) return;
+
+ int i = intensity * last / 255;
+ int t = intensity * last % 255;
+
+ int j = t != 0 ? i : i - 1;
+
+ if (j >= last)
+ j = 0;
+
+ // Get the exact color if any, the next color otherwise.
+ int r2 = mColors[j].value[0],
+ g2 = mColors[j].value[1],
+ b2 = mColors[j].value[2];
+
+ if (t == 0)
+ {
+ // Exact color.
+ color[0] = r2;
+ color[1] = g2;
+ color[2] = b2;
+ return;
+ }
+
+ // Get the previous color. First color is implicitly black.
+ int r1 = 0, g1 = 0, b1 = 0;
+ if (i > 0)
+ {
+ r1 = mColors[i - 1].value[0];
+ g1 = mColors[i - 1].value[1];
+ b1 = mColors[i - 1].value[2];
+ }
+
+ // Perform a linear interpolation.
+ color[0] = ((255 - t) * r1 + t * r2) / 255;
+ color[1] = ((255 - t) * g1 + t * g2) / 255;
+ color[2] = ((255 - t) * b1 + t * b2) / 255;
+}
+
+void DyePalette::getColor(double intensity, int color[3]) const
+{
+ // Nothing to do here
+ if (mColors.size() == 0)
+ return;
+
+ // Force range
+ if (intensity > 1.0)
+ intensity = 1.0;
+ else if (intensity < 0.0)
+ intensity = 0.0;
+
+ // Scale up
+ intensity = intensity * static_cast<double>(mColors.size() - 1);
+
+ // Color indices
+ int i = static_cast<int>(floor(intensity));
+ int j = static_cast<int>(ceil(intensity));
+
+ if (i == j)
+ {
+ // Exact color.
+ color[0] = mColors[i].value[0];
+ color[1] = mColors[i].value[1];
+ color[2] = mColors[i].value[2];
+ return;
+ }
+
+ intensity -= i;
+ double rest = 1 - intensity;
+
+ // Get the colors
+ int r1 = mColors[i].value[0],
+ g1 = mColors[i].value[1],
+ b1 = mColors[i].value[2],
+ r2 = mColors[j].value[0],
+ g2 = mColors[j].value[1],
+ b2 = mColors[j].value[2];
+
+ // Perform the interpolation.
+ color[0] = static_cast<int>(rest * r1 + intensity * r2);
+ color[1] = static_cast<int>(rest * g1 + intensity * g2);
+ color[2] = static_cast<int>(rest * b1 + intensity * b2);
+}
+
+Dye::Dye(const std::string &description)
+{
+ for (int i = 0; i < 7; ++i)
+ mDyePalettes[i] = 0;
+
+ if (description.empty())
+ return;
+
+ std::string::size_type next_pos = 0, length = description.length();
+ do
+ {
+ std::string::size_type pos = next_pos;
+ next_pos = description.find(';', pos);
+
+ if (next_pos == std::string::npos)
+ next_pos = length;
+
+ if (next_pos <= pos + 3 || description[pos + 1] != ':')
+ {
+ logger->log("Error, invalid dye: %s", description.c_str());
+ return;
+ }
+
+ int i = 0;
+
+ switch (description[pos])
+ {
+ case 'R': i = 0; break;
+ case 'G': i = 1; break;
+ case 'Y': i = 2; break;
+ case 'B': i = 3; break;
+ case 'M': i = 4; break;
+ case 'C': i = 5; break;
+ case 'W': i = 6; break;
+ default:
+ logger->log("Error, invalid dye: %s", description.c_str());
+ return;
+ }
+ mDyePalettes[i] = new DyePalette(description.substr(
+ pos + 2, next_pos - pos - 2));
+ ++next_pos;
+ }
+ while (next_pos < length);
+}
+
+Dye::~Dye()
+{
+ for (int i = 0; i < 7; ++i)
+ {
+ delete mDyePalettes[i];
+ mDyePalettes[i] = 0;
+ }
+}
+
+void Dye::update(int color[3]) const
+{
+ int cmax = std::max(color[0], std::max(color[1], color[2]));
+ if (cmax == 0)
+ return;
+
+ int cmin = std::min(color[0], std::min(color[1], color[2]));
+ int intensity = color[0] + color[1] + color[2];
+
+ if (cmin != cmax &&
+ (cmin != 0 || (intensity != cmax && intensity != 2 * cmax)))
+ {
+ // not pure
+ return;
+ }
+
+ int i = (color[0] != 0) | ((color[1] != 0) << 1) | ((color[2] != 0) << 2);
+
+ if (mDyePalettes[i - 1])
+ mDyePalettes[i - 1]->getColor(cmax, color);
+}
+
+void Dye::instantiate(std::string &target, const std::string &palettes)
+{
+ std::string::size_type next_pos = target.find('|');
+
+ if (next_pos == std::string::npos || palettes.empty())
+ return;
+
+ ++next_pos;
+
+ std::ostringstream s;
+ s << target.substr(0, next_pos);
+ std::string::size_type last_pos = target.length(), pal_pos = 0;
+ do
+ {
+ std::string::size_type pos = next_pos;
+ next_pos = target.find(';', pos);
+
+ if (next_pos == std::string::npos)
+ next_pos = last_pos;
+
+ if (next_pos == pos + 1 && pal_pos != std::string::npos)
+ {
+ std::string::size_type pal_next_pos = palettes.find(';', pal_pos);
+ s << target[pos] << ':';
+ if (pal_next_pos == std::string::npos)
+ {
+ s << palettes.substr(pal_pos);
+ s << target.substr(next_pos);
+ pal_pos = std::string::npos;
+ break;
+ }
+ s << palettes.substr(pal_pos, pal_next_pos - pal_pos);
+ pal_pos = pal_next_pos + 1;
+ }
+ else if (next_pos > pos + 2)
+ {
+ s << target.substr(pos, next_pos - pos);
+ }
+ else
+ {
+ logger->log("Error, invalid dye placeholder: %s", target.c_str());
+ return;
+ }
+ s << target[next_pos];
+ ++next_pos;
+ }
+ while (next_pos < last_pos);
+
+ target = s.str();
+}
diff --git a/src/resources/dye.h b/src/resources/dye.h
new file mode 100644
index 000000000..b2f62547a
--- /dev/null
+++ b/src/resources/dye.h
@@ -0,0 +1,107 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DYE_H
+#define DYE_H
+
+#include <string>
+#include <vector>
+
+/**
+ * Class for performing a linear interpolation between colors.
+ */
+class DyePalette
+{
+ public:
+
+ /**
+ * Creates a palette based on the given string.
+ * The string is either a file name or a sequence of hexadecimal RGB
+ * values separated by ',' and starting with '#'.
+ */
+ DyePalette(const std::string &pallete);
+
+/*
+ void addFirstColor(const int color[3]);
+
+ void addLastColor(const int color[3]);
+*/
+
+ /**
+ * Gets a pixel color depending on its intensity. First color is
+ * implicitly black (0, 0, 0).
+ */
+ void getColor(int intensity, int color[3]) const;
+
+ /**
+ * Gets a pixel color depending on its intensity.
+ */
+ void getColor(double intensity, int color[3]) const;
+
+ private:
+
+ struct Color { unsigned char value[3]; };
+
+ std::vector< Color > mColors;
+};
+
+/**
+ * Class for dispatching pixel-recoloring amongst several palettes.
+ */
+class Dye
+{
+ public:
+
+ /**
+ * Creates a set of palettes based on the given string.
+ *
+ * The parts of string are separated by semi-colons. Each part starts
+ * by an uppercase letter, followed by a colon and then a palette name.
+ */
+ Dye(const std::string &dye);
+
+ /**
+ * Destroys the associated palettes.
+ */
+ ~Dye();
+
+ /**
+ * Modifies a pixel color.
+ */
+ void update(int color[3]) const;
+
+ /**
+ * Fills the blank in a dye placeholder with some palette names.
+ */
+ static void instantiate(std::string &target,
+ const std::string &palettes);
+
+ private:
+
+ /**
+ * The order of the palettes, as well as their uppercase letter, is:
+ *
+ * Red, Green, Yellow, Blue, Magenta, White (or rather gray).
+ */
+ DyePalette *mDyePalettes[7];
+};
+
+#endif
diff --git a/src/resources/emotedb.cpp b/src/resources/emotedb.cpp
new file mode 100644
index 000000000..4d0ba34b4
--- /dev/null
+++ b/src/resources/emotedb.cpp
@@ -0,0 +1,222 @@
+/*
+ * Emote database
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/emotedb.h"
+
+#include "animatedsprite.h"
+#include "log.h"
+
+#include "utils/xml.h"
+#include "configuration.h"
+
+namespace
+{
+ EmoteInfos mEmoteInfos;
+ EmoteInfo mUnknown;
+ bool mLoaded = false;
+ int mLastEmote = 0;
+}
+
+void EmoteDB::load()
+{
+ if (mLoaded)
+ unload();
+
+ mLastEmote = 0;
+
+ EmoteSprite *unknownSprite = new EmoteSprite;
+ unknownSprite->sprite = AnimatedSprite::load(
+ paths.getStringValue("spriteErrorFile"));
+ unknownSprite->name = "unknown";
+ mUnknown.sprites.push_back(unknownSprite);
+
+ logger->log1("Initializing emote database...");
+
+ XML::Document doc("emotes.xml");
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "emotes"))
+ {
+ logger->log1("Emote Database: Error while loading emotes.xml!");
+ return;
+ }
+
+ //iterate <emote>s
+ for_each_xml_child_node(emoteNode, rootNode)
+ {
+ if (!xmlStrEqual(emoteNode->name, BAD_CAST "emote"))
+ continue;
+
+ int id = XML::getProperty(emoteNode, "id", -1);
+ if (id == -1)
+ {
+ logger->log1("Emote Database: Emote with missing ID in "
+ "emotes.xml!");
+ continue;
+ }
+
+ EmoteInfo *currentInfo = new EmoteInfo;
+
+ for_each_xml_child_node(spriteNode, emoteNode)
+ {
+ if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite"))
+ {
+ EmoteSprite *currentSprite = new EmoteSprite;
+ std::string file = paths.getStringValue("sprites")
+ + (std::string) (const char*)
+ spriteNode->xmlChildrenNode->content;
+ currentSprite->sprite = AnimatedSprite::load(file,
+ XML::getProperty(spriteNode, "variant", 0));
+ currentSprite->name = XML::getProperty(spriteNode, "name", "");
+ currentInfo->sprites.push_back(currentSprite);
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx"))
+ {
+ std::string particlefx = (const char*)(
+ spriteNode->xmlChildrenNode->content);
+ currentInfo->particles.push_back(particlefx);
+ }
+ }
+ mEmoteInfos[id] = currentInfo;
+ if (id > mLastEmote)
+ mLastEmote = id;
+ }
+
+
+ XML::Document doc2("graphics/sprites/manaplus_emotes.xml");
+ rootNode = doc2.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "emotes"))
+ {
+ logger->log1("Emote Database: Error while loading"
+ " manaplus_emotes.xml!");
+ return;
+ }
+
+ //iterate <emote>s
+ for_each_xml_child_node(emoteNode, rootNode)
+ {
+ if (!xmlStrEqual(emoteNode->name, BAD_CAST "emote"))
+ continue;
+
+ int id = XML::getProperty(emoteNode, "id", -1);
+ if (id == -1)
+ {
+ logger->log1("Emote Database: Emote with missing ID in "
+ "manaplus_emotes.xml!");
+ continue;
+ }
+
+ EmoteInfo *currentInfo = new EmoteInfo;
+
+ for_each_xml_child_node(spriteNode, emoteNode)
+ {
+ if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite"))
+ {
+ EmoteSprite *currentSprite = new EmoteSprite;
+ std::string file = paths.getStringValue("sprites")
+ + (std::string) (const char*)
+ spriteNode->xmlChildrenNode->content;
+ currentSprite->sprite = AnimatedSprite::load(file,
+ XML::getProperty(spriteNode, "variant", 0));
+ currentSprite->name = XML::getProperty(spriteNode, "name", "");
+ currentInfo->sprites.push_back(currentSprite);
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx"))
+ {
+ std::string particlefx = (const char*)(
+ spriteNode->xmlChildrenNode->content);
+ currentInfo->particles.push_back(particlefx);
+ }
+ }
+ mEmoteInfos[id] = currentInfo;
+ if (id > mLastEmote)
+ mLastEmote = id;
+ }
+
+ mLoaded = true;
+}
+
+void EmoteDB::unload()
+{
+ for (EmoteInfos::const_iterator i = mEmoteInfos.begin();
+ i != mEmoteInfos.end();
+ i++)
+ {
+ while (!i->second->sprites.empty())
+ {
+ delete i->second->sprites.front()->sprite;
+ delete i->second->sprites.front();
+ i->second->sprites.pop_front();
+ }
+ delete i->second;
+ }
+
+ mEmoteInfos.clear();
+
+ while (!mUnknown.sprites.empty())
+ {
+ delete mUnknown.sprites.front()->sprite;
+ delete mUnknown.sprites.front();
+ mUnknown.sprites.pop_front();
+ }
+
+ mLoaded = false;
+}
+
+const EmoteInfo *EmoteDB::get(int id, bool allowNull)
+{
+ EmoteInfos::const_iterator i = mEmoteInfos.find(id);
+
+ if (i == mEmoteInfos.end())
+ {
+ if (allowNull)
+ return NULL;
+ logger->log("EmoteDB: Warning, unknown emote ID %d requested", id);
+ return &mUnknown;
+ }
+ else
+ {
+ return i->second;
+ }
+}
+
+const AnimatedSprite *EmoteDB::getAnimation(int id, bool allowNull)
+{
+ const EmoteInfo *info = get(id, allowNull);
+ if (!info)
+ return NULL;
+
+ return info->sprites.front()->sprite;
+}
+
+const EmoteSprite *EmoteDB::getSprite(int id, bool allowNull)
+{
+ const EmoteInfo *info = get(id, allowNull);
+ if (!info)
+ return NULL;
+
+ return info->sprites.front();
+}
+
+const int &EmoteDB::getLast()
+{
+ return mLastEmote;
+}
diff --git a/src/resources/emotedb.h b/src/resources/emotedb.h
new file mode 100644
index 000000000..f9a4b8f9f
--- /dev/null
+++ b/src/resources/emotedb.h
@@ -0,0 +1,64 @@
+/*
+ * Emote database
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EMOTE_DB_H
+#define EMOTE_DB_H
+
+#include <list>
+#include <map>
+#include <string>
+
+class AnimatedSprite;
+
+struct EmoteSprite
+{
+ const AnimatedSprite *sprite;
+ std::string name;
+};
+
+struct EmoteInfo
+{
+ std::list<EmoteSprite*> sprites;
+ std::list<std::string> particles;
+};
+
+typedef std::map<int, EmoteInfo*> EmoteInfos;
+
+/**
+ * Emote information database.
+ */
+namespace EmoteDB
+{
+ void load();
+
+ void unload();
+
+ const EmoteInfo *get(int id, bool allowNull = false);
+
+ const AnimatedSprite *getAnimation(int id, bool allowNull = false);
+
+ const EmoteSprite *getSprite(int id, bool allowNull = false);
+
+ const int &getLast();
+
+ typedef EmoteInfos::iterator EmoteInfosIterator;
+}
+
+#endif
diff --git a/src/resources/image.cpp b/src/resources/image.cpp
new file mode 100644
index 000000000..78cec909b
--- /dev/null
+++ b/src/resources/image.cpp
@@ -0,0 +1,797 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/image.h"
+
+#include "resources/dye.h"
+#include "resources/resourcemanager.h"
+
+#ifdef USE_OPENGL
+#include "openglgraphics.h"
+#include "opengl1graphics.h"
+#endif
+
+#include "log.h"
+#include "main.h"
+
+#include "utils/stringutils.h"
+
+#include <SDL_image.h>
+#include <SDL_rotozoom.h>
+
+#ifdef USE_OPENGL
+int Image::mUseOpenGL = 0;
+int Image::mTextureType = 0;
+int Image::mTextureSize = 0;
+#endif
+bool Image::mEnableAlphaCache = false;
+bool Image::mEnableAlpha = true;
+
+Image::Image(SDL_Surface *image, bool hasAlphaChannel, Uint8 *alphaChannel):
+ mAlpha(1.0f),
+ mHasAlphaChannel(hasAlphaChannel),
+ mSDLSurface(image),
+ mAlphaChannel(alphaChannel)
+{
+#ifdef USE_OPENGL
+ mGLImage = 0;
+#endif
+
+ mUseAlphaCache = Image::mEnableAlphaCache;
+
+ mBounds.x = 0;
+ mBounds.y = 0;
+
+ mLoaded = false;
+
+ if (mSDLSurface)
+ {
+ mBounds.w = static_cast<Uint16>(mSDLSurface->w);
+ mBounds.h = static_cast<Uint16>(mSDLSurface->h);
+
+ mLoaded = true;
+ }
+ else
+ {
+ logger->log(
+ "Image::Image(SDL_Surface*): Couldn't load invalid Surface!");
+ }
+}
+
+#ifdef USE_OPENGL
+Image::Image(GLuint glimage, int width, int height,
+ int texWidth, int texHeight):
+ mAlpha(1.0f),
+ mHasAlphaChannel(true),
+ mSDLSurface(0),
+ mAlphaChannel(0),
+ mUseAlphaCache(false),
+ mGLImage(glimage),
+ mTexWidth(texWidth),
+ mTexHeight(texHeight)
+{
+ mBounds.x = 0;
+ mBounds.y = 0;
+ mBounds.w = static_cast<Uint16>(width);
+ mBounds.h = static_cast<Uint16>(height);
+
+ if (mGLImage)
+ {
+ mLoaded = true;
+ }
+ else
+ {
+ logger->log(
+ "Image::Image(GLuint*, ...): Couldn't load invalid Surface!");
+ mLoaded = false;
+ }
+}
+#endif
+
+Image::~Image()
+{
+ unload();
+}
+
+Resource *Image::load(void *buffer, unsigned bufferSize)
+{
+ // Load the raw file data from the buffer in an RWops structure
+ SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize);
+ SDL_Surface *tmpImage = IMG_Load_RW(rw, 1);
+
+ if (!tmpImage)
+ {
+ logger->log("Error, image load failed: %s", IMG_GetError());
+ return NULL;
+ }
+
+ Image *image = load(tmpImage);
+
+ SDL_FreeSurface(tmpImage);
+ return image;
+}
+
+Resource *Image::load(void *buffer, unsigned bufferSize, Dye const &dye)
+{
+ SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize);
+ SDL_Surface *tmpImage = IMG_Load_RW(rw, 1);
+
+ if (!tmpImage)
+ {
+ logger->log("Error, image load failed: %s", IMG_GetError());
+ return NULL;
+ }
+
+ SDL_PixelFormat rgba;
+ rgba.palette = NULL;
+ rgba.BitsPerPixel = 32;
+ rgba.BytesPerPixel = 4;
+ rgba.Rmask = 0xFF000000; rgba.Rloss = 0; rgba.Rshift = 24;
+ rgba.Gmask = 0x00FF0000; rgba.Gloss = 0; rgba.Gshift = 16;
+ rgba.Bmask = 0x0000FF00; rgba.Bloss = 0; rgba.Bshift = 8;
+ rgba.Amask = 0x000000FF; rgba.Aloss = 0; rgba.Ashift = 0;
+ rgba.colorkey = 0;
+ rgba.alpha = 255;
+
+ SDL_Surface *surf = SDL_ConvertSurface(tmpImage, &rgba, SDL_SWSURFACE);
+ SDL_FreeSurface(tmpImage);
+
+ Uint32 *pixels = static_cast< Uint32 * >(surf->pixels);
+ for (Uint32 *p_end = pixels + surf->w * surf->h; pixels != p_end; ++pixels)
+ {
+ int alpha = *pixels & 255;
+ if (!alpha) continue;
+ int v[3];
+ v[0] = (*pixels >> 24) & 255;
+ v[1] = (*pixels >> 16) & 255;
+ v[2] = (*pixels >> 8 ) & 255;
+ dye.update(v);
+ *pixels = (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | alpha;
+ }
+
+ Image *image = load(surf);
+ SDL_FreeSurface(surf);
+ return image;
+}
+
+Image *Image::load(SDL_Surface *tmpImage)
+{
+#ifdef USE_OPENGL
+ if (mUseOpenGL)
+ return _GLload(tmpImage);
+#endif
+ return _SDLload(tmpImage);
+}
+
+void Image::SDLCleanCache()
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ for (std::map<float, SDL_Surface*>::iterator
+ i = mAlphaCache.begin(), i_end = mAlphaCache.end();
+ i != i_end; ++i)
+ {
+ if (mSDLSurface != i->second)
+ resman->scheduleDelete(i->second);
+ i->second = 0;
+ }
+ mAlphaCache.clear();
+}
+
+void Image::unload()
+{
+ mLoaded = false;
+
+ if (mSDLSurface)
+ {
+ SDLCleanCache();
+ // Free the image surface.
+ SDL_FreeSurface(mSDLSurface);
+ mSDLSurface = NULL;
+
+ delete[] mAlphaChannel;
+ mAlphaChannel = NULL;
+ }
+
+#ifdef USE_OPENGL
+ if (mGLImage)
+ {
+ glDeleteTextures(1, &mGLImage);
+ mGLImage = 0;
+ }
+#endif
+}
+
+int Image::useOpenGL() const
+{
+#ifdef USE_OPENGL
+ return mUseOpenGL;
+#else
+ return 0;
+#endif
+}
+
+bool Image::hasAlphaChannel()
+{
+ if (mLoaded)
+ return mHasAlphaChannel;
+
+#ifdef USE_OPENGL
+ if (mUseOpenGL)
+ return true;
+#endif
+
+ return false;
+}
+
+SDL_Surface *Image::getByAlpha(float alpha)
+{
+ std::map<float, SDL_Surface*>::iterator it = mAlphaCache.find(alpha);
+ if (it != mAlphaCache.end())
+ return (*it).second;
+ return 0;
+}
+
+void Image::setAlpha(float alpha)
+{
+ if (mAlpha == alpha || !mEnableAlpha)
+ return;
+
+ if (alpha < 0.0f || alpha > 1.0f)
+ return;
+
+ if (mSDLSurface)
+ {
+ if (mUseAlphaCache)
+ {
+ SDL_Surface *surface = getByAlpha(mAlpha);
+ if (!surface)
+ {
+ if (mAlphaCache.size() > 100)
+ {
+#ifdef DEBUG_ALPHA_CACHE
+ logger->log("cleanCache");
+ for (std::map<float, SDL_Surface*>::iterator
+ i = mAlphaCache.begin(), i_end = mAlphaCache.end();
+ i != i_end; ++i)
+ {
+ logger->log("alpha: " + toString(i->first));
+ }
+#endif
+ SDLCleanCache();
+ }
+ surface = mSDLSurface;
+ if (surface)
+ mAlphaCache[mAlpha] = surface;
+ }
+ else
+ {
+ logger->log("cache bug");
+ }
+
+ surface = getByAlpha(alpha);
+ if (surface)
+ {
+// logger->log("hit");
+ if (mSDLSurface == surface)
+ logger->log("bug");
+// else
+// SDL_FreeSurface(mSDLSurface);
+ mAlphaCache.erase(alpha);
+ mSDLSurface = surface;
+ mAlpha = alpha;
+ return;
+ }
+ else
+ {
+ mSDLSurface = Image::SDLDuplicateSurface(mSDLSurface);
+ }
+ // logger->log("miss");
+ }
+
+ mAlpha = alpha;
+
+ if (!hasAlphaChannel())
+ {
+ // Set the alpha value this image is drawn at
+ SDL_SetAlpha(mSDLSurface, SDL_SRCALPHA,
+ static_cast<unsigned char>(255 * mAlpha));
+ }
+ else
+ {
+ if (SDL_MUSTLOCK(mSDLSurface))
+ SDL_LockSurface(mSDLSurface);
+
+ // Precompute as much as possible
+ int maxHeight = std::min((mBounds.y + mBounds.h), mSDLSurface->h);
+ int maxWidth = std::min((mBounds.x + mBounds.w), mSDLSurface->w);
+ int i = 0;
+
+ for (int y = mBounds.y; y < maxHeight; y++)
+ {
+ for (int x = mBounds.x; x < maxWidth; x++)
+ {
+ i = y * mSDLSurface->w + x;
+ // Only change the pixel if it was visible at load time...
+ Uint8 sourceAlpha = mAlphaChannel[i];
+ if (sourceAlpha > 0)
+ {
+ Uint8 r, g, b, a;
+ SDL_GetRGBA((static_cast<Uint32*>
+ (mSDLSurface->pixels))[i],
+ mSDLSurface->format,
+ &r, &g, &b, &a);
+
+ a = (Uint8) (static_cast<float>(sourceAlpha) * mAlpha);
+
+ // Here is the pixel we want to set
+ (static_cast<Uint32 *>(mSDLSurface->pixels))[i] =
+ SDL_MapRGBA(mSDLSurface->format, r, g, b, a);
+ }
+ }
+ }
+
+ if (SDL_MUSTLOCK(mSDLSurface))
+ SDL_UnlockSurface(mSDLSurface);
+ }
+ }
+ else
+ {
+ mAlpha = alpha;
+ }
+}
+
+Image* Image::SDLmerge(Image *image, int x, int y)
+{
+ if (!mSDLSurface || !image || !image->mSDLSurface)
+ return NULL;
+
+ SDL_Surface* surface = new SDL_Surface(*(image->mSDLSurface));
+
+ Uint32 surface_pix, cur_pix;
+ Uint8 r, g, b, a, p_r, p_g, p_b, p_a;
+ double f_a, f_ca, f_pa;
+ SDL_PixelFormat *current_fmt = mSDLSurface->format;
+ SDL_PixelFormat *surface_fmt = surface->format;
+ int current_offset, surface_offset;
+ int offset_x, offset_y;
+
+ SDL_LockSurface(surface);
+ SDL_LockSurface(mSDLSurface);
+ // for each pixel lines of a source image
+ for (offset_x = (x > 0 ? 0 : -x); offset_x < image->getWidth() &&
+ x + offset_x < getWidth(); offset_x++)
+ {
+ for (offset_y = (y > 0 ? 0 : -y); offset_y < image->getHeight()
+ && y + offset_y < getHeight(); offset_y++)
+ {
+ // Computing offset on both images
+ current_offset = (y + offset_y) * getWidth() + x + offset_x;
+ surface_offset = offset_y * surface->w + offset_x;
+
+ // Retrieving a pixel to merge
+ surface_pix = ((Uint32*) surface->pixels)[surface_offset];
+ cur_pix = ((Uint32*) mSDLSurface->pixels)[current_offset];
+
+ // Retreiving each channel of the pixel using pixel format
+ r = (Uint8)(((surface_pix & surface_fmt->Rmask) >>
+ surface_fmt->Rshift) << surface_fmt->Rloss);
+ g = (Uint8)(((surface_pix & surface_fmt->Gmask) >>
+ surface_fmt->Gshift) << surface_fmt->Gloss);
+ b = (Uint8)(((surface_pix & surface_fmt->Bmask) >>
+ surface_fmt->Bshift) << surface_fmt->Bloss);
+ a = (Uint8)(((surface_pix & surface_fmt->Amask) >>
+ surface_fmt->Ashift) << surface_fmt->Aloss);
+
+ // Retreiving previous alpha value
+ p_a = (Uint8)(((cur_pix & current_fmt->Amask) >>
+ current_fmt->Ashift) << current_fmt->Aloss);
+
+ // new pixel with no alpha or nothing on previous pixel
+ if (a == SDL_ALPHA_OPAQUE || (p_a == 0 && a > 0))
+ ((Uint32 *)(surface->pixels))[current_offset] =
+ SDL_MapRGBA(current_fmt, r, g, b, a);
+ else if (a > 0)
+ { // alpha is lower => merge color with previous value
+ f_a = static_cast<double>(a) / 255.0;
+ f_ca = 1.0 - f_a;
+ f_pa = static_cast<double>(p_a) / 255.0;
+ p_r = (Uint8)(((cur_pix & current_fmt->Rmask) >>
+ current_fmt->Rshift) << current_fmt->Rloss);
+ p_g = (Uint8)(((cur_pix & current_fmt->Gmask) >>
+ current_fmt->Gshift) << current_fmt->Gloss);
+ p_b = (Uint8)(((cur_pix & current_fmt->Bmask) >>
+ current_fmt->Bshift) << current_fmt->Bloss);
+ r = (Uint8)((double) p_r * f_ca * f_pa + (double)r * f_a);
+ g = (Uint8)((double) p_g * f_ca * f_pa + (double)g * f_a);
+ b = (Uint8)((double) p_b * f_ca * f_pa + (double)b * f_a);
+ a = (a > p_a ? a : p_a);
+ ((Uint32 *)(surface->pixels))[current_offset] =
+ SDL_MapRGBA(current_fmt, r, g, b, a);
+ }
+ }
+ }
+ SDL_UnlockSurface(surface);
+ SDL_UnlockSurface(mSDLSurface);
+
+ Image *newImage = new Image(surface);
+
+ return newImage;
+}
+
+Image* Image::SDLgetScaledImage(int width, int height)
+{
+ // No scaling on incorrect new values.
+ if (width == 0 || height == 0)
+ return NULL;
+
+ // No scaling when there is ... no different given size ...
+ if (width == getWidth() && height == getHeight())
+ return NULL;
+
+ Image* scaledImage = NULL;
+ SDL_Surface* scaledSurface = NULL;
+
+ if (mSDLSurface)
+ {
+ scaledSurface = zoomSurface(mSDLSurface,
+ (double) width / getWidth(),
+ (double) height / getHeight(),
+ 1);
+
+ // The load function takes care of the SDL<->OpenGL implementation
+ // and about freeing the given SDL_surface*.
+ if (scaledSurface)
+ scaledImage = load(scaledSurface);
+ }
+ return scaledImage;
+}
+
+SDL_Surface* Image::convertTo32Bit(SDL_Surface* tmpImage)
+{
+ if (!tmpImage)
+ return NULL;
+ SDL_PixelFormat RGBAFormat;
+ RGBAFormat.palette = 0;
+ RGBAFormat.colorkey = 0;
+ RGBAFormat.alpha = 0;
+ RGBAFormat.BitsPerPixel = 32;
+ RGBAFormat.BytesPerPixel = 4;
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ RGBAFormat.Rmask = 0xFF000000;
+ RGBAFormat.Rshift = 0;
+ RGBAFormat.Rloss = 0;
+ RGBAFormat.Gmask = 0x00FF0000;
+ RGBAFormat.Gshift = 8;
+ RGBAFormat.Gloss = 0;
+ RGBAFormat.Bmask = 0x0000FF00;
+ RGBAFormat.Bshift = 16;
+ RGBAFormat.Bloss = 0;
+ RGBAFormat.Amask = 0x000000FF;
+ RGBAFormat.Ashift = 24;
+ RGBAFormat.Aloss = 0;
+#else
+ RGBAFormat.Rmask = 0x000000FF;
+ RGBAFormat.Rshift = 24;
+ RGBAFormat.Rloss = 0;
+ RGBAFormat.Gmask = 0x0000FF00;
+ RGBAFormat.Gshift = 16;
+ RGBAFormat.Gloss = 0;
+ RGBAFormat.Bmask = 0x00FF0000;
+ RGBAFormat.Bshift = 8;
+ RGBAFormat.Bloss = 0;
+ RGBAFormat.Amask = 0xFF000000;
+ RGBAFormat.Ashift = 0;
+ RGBAFormat.Aloss = 0;
+#endif
+ return SDL_ConvertSurface(tmpImage, &RGBAFormat, SDL_SWSURFACE);
+}
+
+SDL_Surface* Image::SDLDuplicateSurface(SDL_Surface* tmpImage)
+{
+ if (!tmpImage || !tmpImage->format)
+ return NULL;
+
+ return SDL_ConvertSurface(tmpImage, tmpImage->format, SDL_SWSURFACE);
+}
+
+Image *Image::_SDLload(SDL_Surface *tmpImage)
+{
+ if (!tmpImage)
+ return NULL;
+
+ bool hasAlpha = false;
+ bool converted = false;
+
+ // The alpha channel to be filled with alpha values
+ Uint8 *alphaChannel = new Uint8[tmpImage->w * tmpImage->h];
+
+ if (tmpImage->format->BitsPerPixel != 32)
+ {
+ tmpImage = convertTo32Bit(tmpImage);
+
+ if (!tmpImage)
+ {
+ delete[] alphaChannel;
+ return NULL;
+ }
+ converted = true;
+ }
+
+ // Figure out whether the image uses its alpha layer
+ for (int i = 0; i < tmpImage->w * tmpImage->h; ++i)
+ {
+ Uint8 r, g, b, a;
+ SDL_GetRGBA(((Uint32*) tmpImage->pixels)[i],
+ tmpImage->format, &r, &g, &b, &a);
+
+ if (a != 255)
+ hasAlpha = true;
+
+ alphaChannel[i] = a;
+ }
+
+ SDL_Surface *image;
+
+ // Convert the surface to the current display format
+ if (hasAlpha)
+ {
+ image = SDL_DisplayFormatAlpha(tmpImage);
+ }
+ else
+ {
+ image = SDL_DisplayFormat(tmpImage);
+
+ // We also delete the alpha channel since
+ // it's not used.
+ delete[] alphaChannel;
+ alphaChannel = 0;
+ }
+
+ if (!image)
+ {
+ logger->log1("Error: Image convert failed.");
+ delete[] alphaChannel;
+ return 0;
+ }
+
+ if (converted)
+ SDL_FreeSurface(tmpImage);
+
+ return new Image(image, hasAlpha, alphaChannel);
+}
+
+#ifdef USE_OPENGL
+Image *Image::_GLload(SDL_Surface *tmpImage)
+{
+ if (!tmpImage)
+ return NULL;
+
+ // Flush current error flag.
+ glGetError();
+
+ int width = tmpImage->w;
+ int height = tmpImage->h;
+ int realWidth = powerOfTwo(width);
+ int realHeight = powerOfTwo(height);
+
+ if (realWidth < width || realHeight < height)
+ {
+ logger->log("Warning: image too large, cropping to %dx%d texture!",
+ tmpImage->w, tmpImage->h);
+ }
+
+ // Make sure the alpha channel is not used, but copied to destination
+ SDL_SetAlpha(tmpImage, 0, SDL_ALPHA_OPAQUE);
+
+ // Determine 32-bit masks based on byte order
+ Uint32 rmask, gmask, bmask, amask;
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ rmask = 0xff000000;
+ gmask = 0x00ff0000;
+ bmask = 0x0000ff00;
+ amask = 0x000000ff;
+#else
+ rmask = 0x000000ff;
+ gmask = 0x0000ff00;
+ bmask = 0x00ff0000;
+ amask = 0xff000000;
+#endif
+
+ SDL_Surface *oldImage = tmpImage;
+ tmpImage = SDL_CreateRGBSurface(SDL_SWSURFACE, realWidth, realHeight,
+ 32, rmask, gmask, bmask, amask);
+
+ if (!tmpImage)
+ {
+ logger->log("Error, image convert failed: out of memory");
+ return NULL;
+ }
+
+ SDL_BlitSurface(oldImage, NULL, tmpImage, NULL);
+
+ GLuint texture;
+ glGenTextures(1, &texture);
+ if (mUseOpenGL == 1)
+ OpenGLGraphics::bindTexture(mTextureType, texture);
+ else if (mUseOpenGL == 2)
+// glBindTexture(mTextureType, texture);
+ OpenGL1Graphics::bindTexture(mTextureType, texture);
+
+ if (SDL_MUSTLOCK(tmpImage))
+ SDL_LockSurface(tmpImage);
+
+ glTexImage2D(mTextureType, 0, 4, tmpImage->w, tmpImage->h,
+ 0, GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->pixels);
+
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+ glTexParameteri(mTextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(mTextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ if (SDL_MUSTLOCK(tmpImage))
+ SDL_UnlockSurface(tmpImage);
+
+ SDL_FreeSurface(tmpImage);
+
+ GLenum error = glGetError();
+ if (error)
+ {
+ std::string errmsg = "Unknown error";
+ switch (error)
+ {
+ case GL_INVALID_ENUM:
+ errmsg = "GL_INVALID_ENUM";
+ break;
+ case GL_INVALID_VALUE:
+ errmsg = "GL_INVALID_VALUE";
+ break;
+ case GL_INVALID_OPERATION:
+ errmsg = "GL_INVALID_OPERATION";
+ break;
+ case GL_STACK_OVERFLOW:
+ errmsg = "GL_STACK_OVERFLOW";
+ break;
+ case GL_STACK_UNDERFLOW:
+ errmsg = "GL_STACK_UNDERFLOW";
+ break;
+ case GL_OUT_OF_MEMORY:
+ errmsg = "GL_OUT_OF_MEMORY";
+ break;
+ default:
+ break;
+ }
+ logger->log("Error: Image GL import failed: %s", errmsg.c_str());
+ return NULL;
+ }
+
+ return new Image(texture, width, height, realWidth, realHeight);
+}
+
+void Image::setLoadAsOpenGL(int useOpenGL)
+{
+ Image::mUseOpenGL = useOpenGL;
+}
+
+int Image::powerOfTwo(int input)
+{
+ int value;
+ if (mTextureType == GL_TEXTURE_2D)
+ {
+ value = 1;
+ while (value < input && value < mTextureSize)
+ value <<= 1;
+ }
+ else
+ {
+ value = input;
+ }
+ return value >= mTextureSize ? mTextureSize : value;
+}
+#endif
+
+Image *Image::getSubImage(int x, int y, int width, int height)
+{
+ // Create a new clipped sub-image
+#ifdef USE_OPENGL
+ if (mUseOpenGL)
+ {
+ return new SubImage(this, mGLImage, x, y, width, height,
+ mTexWidth, mTexHeight);
+ }
+#endif
+
+ return new SubImage(this, mSDLSurface, x, y, width, height);
+}
+
+void Image::SDLTerminateAlphaCache()
+{
+ SDLCleanCache();
+ mUseAlphaCache = false;
+}
+
+//============================================================================
+// SubImage Class
+//============================================================================
+
+SubImage::SubImage(Image *parent, SDL_Surface *image,
+ int x, int y, int width, int height):
+ Image(image),
+ mParent(parent)
+{
+ if (mParent)
+ {
+ mParent->incRef();
+ mParent->SDLTerminateAlphaCache();
+ mHasAlphaChannel = mParent->hasAlphaChannel();
+ mAlphaChannel = mParent->SDLgetAlphaChannel();
+ }
+ else
+ {
+ mHasAlphaChannel = false;
+ mAlphaChannel = 0;
+ }
+
+ // Set up the rectangle.
+ mBounds.x = static_cast<short>(x);
+ mBounds.y = static_cast<short>(y);
+ mBounds.w = static_cast<Uint16>(width);
+ mBounds.h = static_cast<Uint16>(height);
+ mUseAlphaCache = false;
+}
+
+#ifdef USE_OPENGL
+SubImage::SubImage(Image *parent, GLuint image,
+ int x, int y, int width, int height,
+ int texWidth, int texHeight):
+ Image(image, width, height, texWidth, texHeight),
+ mParent(parent)
+{
+ if (mParent)
+ mParent->incRef();
+
+ // Set up the rectangle.
+ mBounds.x = static_cast<short>(x);
+ mBounds.y = static_cast<short>(y);
+ mBounds.w = static_cast<Uint16>(width);
+ mBounds.h = static_cast<Uint16>(height);
+}
+#endif
+
+SubImage::~SubImage()
+{
+ // Avoid destruction of the image
+ mSDLSurface = 0;
+ // Avoid possible destruction of its alpha channel
+ mAlphaChannel = 0;
+#ifdef USE_OPENGL
+ mGLImage = 0;
+#endif
+ if (mParent)
+ mParent->decRef();
+}
+
+Image *SubImage::getSubImage(int x, int y, int w, int h)
+{
+ if (mParent)
+ return mParent->getSubImage(mBounds.x + x, mBounds.y + y, w, h);
+ else
+ return NULL;
+}
diff --git a/src/resources/image.h b/src/resources/image.h
new file mode 100644
index 000000000..7145b9d48
--- /dev/null
+++ b/src/resources/image.h
@@ -0,0 +1,308 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "main.h"
+
+#include "resources/resource.h"
+
+#include <SDL.h>
+
+#ifdef USE_OPENGL
+
+/* The definition of OpenGL extensions by SDL is giving problems with recent
+ * gl.h headers, since they also include these definitions. As we're not using
+ * extensions anyway it's safe to just disable the SDL version.
+ */
+#define NO_SDL_GLEXT
+
+#include <SDL_opengl.h>
+#endif
+
+#include <map>
+
+class Dye;
+class Position;
+
+/**
+ * Defines a class for loading and storing images.
+ */
+class Image : public Resource
+{
+ friend class CompoundSprite;
+ friend class Graphics;
+#ifdef USE_OPENGL
+ friend class OpenGLGraphics;
+ friend class OpenGL1Graphics;
+#endif
+
+ public:
+ /**
+ * Destructor.
+ */
+ virtual ~Image();
+
+ /**
+ * Loads an image from a buffer in memory.
+ *
+ * @param buffer The memory buffer containing the image data.
+ * @param bufferSize The size of the memory buffer in bytes.
+ *
+ * @return <code>NULL</code> if an error occurred, a valid pointer
+ * otherwise.
+ */
+ static Resource *load(void *buffer, unsigned bufferSize);
+
+ /**
+ * Loads an image from a buffer in memory and recolors it.
+ *
+ * @param buffer The memory buffer containing the image data.
+ * @param bufferSize The size of the memory buffer in bytes.
+ * @param dye The dye used to recolor the image.
+ *
+ * @return <code>NULL</code> if an error occurred, a valid pointer
+ * otherwise.
+ */
+ static Resource *load(void *buffer, unsigned bufferSize,
+ Dye const &dye);
+
+ /**
+ * Loads an image from an SDL surface.
+ */
+ static Image *load(SDL_Surface *);
+
+ static SDL_Surface* convertTo32Bit(SDL_Surface* tmpImage);
+
+ /**
+ * Frees the resources created by SDL.
+ */
+ virtual void unload();
+
+ /**
+ * Tells is the image is loaded
+ */
+ bool isLoaded()
+ { return mLoaded; }
+
+ /**
+ * Returns the width of the image.
+ */
+ inline int getWidth() const // was virtual
+ { return mBounds.w; }
+
+ /**
+ * Returns the height of the image.
+ */
+ inline int getHeight() const // was virtual
+ { return mBounds.h; }
+
+ /**
+ * Tells if the image was loaded using OpenGL or SDL
+ * @return true if OpenGL, false if SDL.
+ */
+ int useOpenGL() const;
+
+ /**
+ * Tells if the image has got an alpha channel
+ * @return true if it's true, false otherwise.
+ */
+ bool hasAlphaChannel();
+
+ /**
+ * Sets the alpha value of this image.
+ */
+ virtual void setAlpha(float alpha);
+
+ /**
+ * Returns the alpha value of this image.
+ */
+ float getAlpha() const
+ { return mAlpha; }
+
+ /**
+ * Creates a new image with the desired clipping rectangle.
+ *
+ * @return <code>NULL</code> if creation failed and a valid
+ * object otherwise.
+ */
+ virtual Image *getSubImage(int x, int y, int width, int height);
+
+
+ // SDL only public functions
+
+ /**
+ * Gets an scaled instance of an image.
+ *
+ * @param width The desired width of the scaled image.
+ * @param height The desired height of the scaled image.
+ *
+ * @return A new Image* object.
+ */
+ Image* SDLgetScaledImage(int width, int height);
+
+ /**
+ * Merges two image SDL_Surfaces together. This is for SDL use only, as
+ * reducing the number of surfaces that SDL has to render can cut down
+ * on the number of blit operations necessary, which in turn can help
+ * improve overall framerates. Don't use unless you are using it to
+ * reduce the number of overall layers that need to be drawn through SDL.
+ */
+ Image *SDLmerge(Image *image, int x, int y);
+
+ /**
+ * Get the alpha Channel of a SDL surface.
+ */
+ Uint8 *SDLgetAlphaChannel() const
+ { return mAlphaChannel; }
+
+ SDL_Surface* SDLDuplicateSurface(SDL_Surface* tmpImage);
+
+ void SDLCleanCache();
+
+ void SDLTerminateAlphaCache();
+
+ static void SDLSetEnableAlphaCache(bool n)
+ { mEnableAlphaCache = n; }
+
+ static void setEnableAlpha(bool n)
+ { mEnableAlpha = n; }
+
+#ifdef USE_OPENGL
+
+ // OpenGL only public functions
+
+ /**
+ * Sets the target image format. Use <code>false</code> for SDL and
+ * <code>true</code> for OpenGL.
+ */
+ static void setLoadAsOpenGL(int useOpenGL);
+
+ static int getLoadAsOpenGL()
+ { return mUseOpenGL; }
+
+ int getTextureWidth() const
+ { return mTexWidth; }
+
+ int getTextureHeight() const
+ { return mTexHeight; }
+
+ static int getTextureType()
+ { return mTextureType; }
+#endif
+
+ protected:
+
+ // -----------------------
+ // Generic protected members
+ // -----------------------
+
+ SDL_Rect mBounds;
+ bool mLoaded;
+ float mAlpha;
+ bool mHasAlphaChannel;
+
+ // -----------------------
+ // SDL protected members
+ // -----------------------
+
+ /** SDL Constructor */
+ Image(SDL_Surface *image, bool hasAlphaChannel = false,
+ Uint8 *alphaChannel = NULL);
+
+ /** SDL_Surface to SDL_Surface Image loader */
+ static Image *_SDLload(SDL_Surface *tmpImage);
+
+ SDL_Surface *getByAlpha(float alpha);
+
+ SDL_Surface *mSDLSurface;
+
+ /** Alpha Channel pointer used for 32bit based SDL surfaces */
+ Uint8 *mAlphaChannel;
+
+ std::map<float, SDL_Surface*> mAlphaCache;
+
+ bool mUseAlphaCache;
+
+ static bool mEnableAlphaCache;
+ static bool mEnableAlpha;
+
+ // -----------------------
+ // OpenGL protected members
+ // -----------------------
+#ifdef USE_OPENGL
+ /**
+ * OpenGL Constructor.
+ */
+ Image(GLuint glimage, int width, int height,
+ int texWidth, int texHeight);
+
+ /**
+ * Returns the first power of two equal or bigger than the input.
+ */
+ static int powerOfTwo(int input);
+
+ static Image *_GLload(SDL_Surface *tmpImage);
+
+ GLuint mGLImage;
+ int mTexWidth, mTexHeight;
+
+ static int mUseOpenGL;
+ static int mTextureType;
+ static int mTextureSize;
+#endif
+};
+
+/**
+ * A clipped version of a larger image.
+ */
+class SubImage : public Image
+{
+ public:
+ /**
+ * Constructor.
+ */
+ SubImage(Image *parent, SDL_Surface *image,
+ int x, int y, int width, int height);
+#ifdef USE_OPENGL
+ SubImage(Image *parent, GLuint image, int x, int y,
+ int width, int height, int texWidth, int textHeight);
+#endif
+
+ /**
+ * Destructor.
+ */
+ ~SubImage();
+
+ /**
+ * Creates a new image with the desired clipping rectangle.
+ *
+ * @return <code>NULL</code> if creation failed and a valid
+ * image otherwise.
+ */
+ Image *getSubImage(int x, int y, int width, int height);
+
+ private:
+ Image *mParent;
+};
+
+#endif
diff --git a/src/resources/imageloader.cpp b/src/resources/imageloader.cpp
new file mode 100644
index 000000000..97c7a0146
--- /dev/null
+++ b/src/resources/imageloader.cpp
@@ -0,0 +1,114 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/imageloader.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include <guichan/color.hpp>
+
+#include <guichan/sdl/sdlpixel.hpp>
+
+#include <cassert>
+
+ProxyImage::ProxyImage(SDL_Surface *s):
+ mImage(NULL), mSDLImage(s)
+{
+}
+
+ProxyImage::~ProxyImage()
+{
+ free();
+}
+
+void ProxyImage::free()
+{
+ if (mSDLImage)
+ {
+ SDL_FreeSurface(mSDLImage);
+ mSDLImage = 0;
+ }
+ else
+ {
+ delete mImage;
+ mImage = 0;
+ }
+}
+
+int ProxyImage::getWidth() const
+{
+ if (mSDLImage)
+ return mSDLImage->w;
+ else if (mImage)
+ return mImage->getWidth();
+ else
+ return 0;
+// return mSDLImage ? mSDLImage->w : mImage->getWidth();
+}
+
+int ProxyImage::getHeight() const
+{
+ if (mSDLImage)
+ return mSDLImage->h;
+ else if (mImage)
+ return mImage->getHeight();
+ else
+ return 0;
+// return mSDLImage ? mSDLImage->h : mImage->getHeight();
+}
+
+gcn::Color ProxyImage::getPixel(int x, int y)
+{
+ assert(mSDLImage);
+ return gcn::SDLgetPixel(mSDLImage, x, y);
+}
+
+void ProxyImage::putPixel(int x, int y, gcn::Color const &color)
+{
+ if (!mSDLImage)
+ return;
+ gcn::SDLputPixel(mSDLImage, x, y, color);
+}
+
+void ProxyImage::convertToDisplayFormat()
+{
+ if (!mSDLImage)
+ return;
+
+ /* The picture is most probably full of the pink pixels Guichan uses for
+ transparency, as this function will only be called when setting an image
+ font. Get rid of them. */
+ SDL_SetColorKey(mSDLImage, SDL_SRCCOLORKEY,
+ SDL_MapRGB(mSDLImage->format, 255, 0, 255));
+ mImage = ::Image::load(mSDLImage);
+ SDL_FreeSurface(mSDLImage);
+ mSDLImage = NULL;
+}
+
+gcn::Image *ImageLoader::load(const std::string &filename, bool convert)
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+ ProxyImage *i = new ProxyImage(resman->loadSDLSurface(filename));
+ if (convert)
+ i->convertToDisplayFormat();
+ return i;
+}
diff --git a/src/resources/imageloader.h b/src/resources/imageloader.h
new file mode 100644
index 000000000..7f53ff240
--- /dev/null
+++ b/src/resources/imageloader.h
@@ -0,0 +1,69 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2007-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IMAGELOADER_H
+#define IMAGELOADER_H
+
+#include <guichan/image.hpp>
+#include <guichan/imageloader.hpp>
+
+#include <string>
+
+class Image;
+struct SDL_Surface;
+
+class ProxyImage : public gcn::Image
+{
+ public:
+ ProxyImage(SDL_Surface *);
+ ~ProxyImage();
+
+ void free();
+ int getWidth() const;
+ int getHeight() const;
+ gcn::Color getPixel(int x, int y);
+ void putPixel(int x, int y, gcn::Color const &color);
+ void convertToDisplayFormat();
+
+ /**
+ * Gets the image handled by this proxy.
+ */
+ ::Image *getImage() const
+ { return mImage; }
+
+ private:
+ ::Image *mImage; /**< The real image. */
+
+ /**
+ * An SDL surface kept around until Guichan is done reading pixels from
+ * an OpenGL texture.
+ */
+ SDL_Surface *mSDLImage;
+};
+
+class ImageLoader : public gcn::ImageLoader
+{
+ public:
+ gcn::Image *load(const std::string &filename,
+ bool convertToDisplayFormat);
+};
+
+#endif
diff --git a/src/resources/imageset.cpp b/src/resources/imageset.cpp
new file mode 100644
index 000000000..93d3138aa
--- /dev/null
+++ b/src/resources/imageset.cpp
@@ -0,0 +1,64 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/imageset.h"
+
+#include "log.h"
+
+#include "resources/image.h"
+
+#include "utils/dtor.h"
+
+ImageSet::ImageSet(Image *img, int width, int height, int margin, int spacing)
+{
+ if (img)
+ {
+ for (int y = margin; y + height <= img->getHeight() - margin;
+ y += height + spacing)
+ {
+ for (int x = margin; x + width <= img->getWidth() - margin;
+ x += width + spacing)
+ {
+ mImages.push_back(img->getSubImage(x, y, width, height));
+ }
+ }
+ }
+ mWidth = width;
+ mHeight = height;
+}
+
+ImageSet::~ImageSet()
+{
+ delete_all(mImages);
+}
+
+Image* ImageSet::get(size_type i) const
+{
+ if (i >= mImages.size())
+ {
+ logger->log("Warning: No sprite %d in this image set", (int) i);
+ return NULL;
+ }
+ else
+ {
+ return mImages[i];
+ }
+}
diff --git a/src/resources/imageset.h b/src/resources/imageset.h
new file mode 100644
index 000000000..bfb834128
--- /dev/null
+++ b/src/resources/imageset.h
@@ -0,0 +1,72 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IMAGESET_H
+#define IMAGESET_H
+
+#include "resources/resource.h"
+
+#include <vector>
+
+class Image;
+
+/**
+ * Stores a set of subimages originating from a single image.
+ */
+class ImageSet : public Resource
+{
+ public:
+ /**
+ * Cuts the passed image in a grid of sub images.
+ */
+ ImageSet(Image *img, int w, int h, int margin = 0, int spacing = 0);
+
+ /**
+ * Destructor.
+ */
+ ~ImageSet();
+
+ /**
+ * Returns the width of the images in the image set.
+ */
+ int getWidth() const
+ { return mWidth; }
+
+ /**
+ * Returns the height of the images in the image set.
+ */
+ int getHeight() const
+ { return mHeight; }
+
+ typedef std::vector<Image*>::size_type size_type;
+ Image* get(size_type i) const;
+
+ size_type size() const
+ { return mImages.size(); }
+
+ private:
+ std::vector<Image*> mImages;
+
+ int mHeight; /**< Height of the images in the image set. */
+ int mWidth; /**< Width of the images in the image set. */
+};
+
+#endif
diff --git a/src/resources/imagewriter.cpp b/src/resources/imagewriter.cpp
new file mode 100644
index 000000000..f3f4be2b4
--- /dev/null
+++ b/src/resources/imagewriter.cpp
@@ -0,0 +1,111 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/imagewriter.h"
+
+#include "log.h"
+
+#include <png.h>
+#include <SDL.h>
+#include <string>
+
+bool ImageWriter::writePNG(SDL_Surface *surface, const std::string &filename)
+{
+ // TODO Maybe someone can make this look nice?
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_bytep *row_pointers;
+ int colortype;
+
+ if (SDL_MUSTLOCK(surface))
+ SDL_LockSurface(surface);
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+ if (!png_ptr)
+ {
+ logger->log1("Had trouble creating png_structp");
+ return false;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ {
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ logger->log1("Could not create png_info");
+ return false;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ {
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ logger->log("problem writing to %s", filename.c_str());
+ return false;
+ }
+
+ FILE *fp = fopen(filename.c_str(), "wb");
+ if (!fp)
+ {
+ logger->log("could not open file %s for writing", filename.c_str());
+ return false;
+ }
+
+ png_init_io(png_ptr, fp);
+
+ colortype = (surface->format->BitsPerPixel == 24) ?
+ PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+
+ png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ png_write_info(png_ptr, info_ptr);
+
+ png_set_packing(png_ptr);
+
+ row_pointers = new png_bytep[surface->h];
+ if (!row_pointers)
+ {
+ logger->log1("Had trouble converting surface to row pointers");
+ fclose(fp);
+ return false;
+ }
+
+ for (int i = 0; i < surface->h; i++)
+ {
+ row_pointers[i] = (png_bytep)(Uint8 *)surface->pixels
+ + i * surface->pitch;
+ }
+
+ png_write_image(png_ptr, row_pointers);
+ png_write_end(png_ptr, info_ptr);
+
+ fclose(fp);
+
+ delete [] row_pointers;
+
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+
+ if (SDL_MUSTLOCK(surface))
+ SDL_UnlockSurface(surface);
+
+ return true;
+}
diff --git a/src/resources/imagewriter.h b/src/resources/imagewriter.h
new file mode 100644
index 000000000..ac07320a6
--- /dev/null
+++ b/src/resources/imagewriter.h
@@ -0,0 +1,31 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <iosfwd>
+
+struct SDL_Surface;
+
+class ImageWriter
+{
+ public:
+ static bool writePNG(SDL_Surface *surface,
+ const std::string &filename);
+};
diff --git a/src/resources/itemdb.cpp b/src/resources/itemdb.cpp
new file mode 100644
index 000000000..cc70e33b7
--- /dev/null
+++ b/src/resources/itemdb.cpp
@@ -0,0 +1,463 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/itemdb.h"
+
+#include "log.h"
+
+#include "resources/iteminfo.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+#include "configuration.h"
+
+#include <libxml/tree.h>
+
+#include <cassert>
+
+namespace
+{
+ ItemDB::ItemInfos mItemInfos;
+ ItemDB::NamedItemInfos mNamedItemInfos;
+ ItemInfo *mUnknown;
+ bool mLoaded = false;
+}
+
+// Forward declarations
+static void loadSpriteRef(ItemInfo *itemInfo, xmlNodePtr node);
+static void loadSoundRef(ItemInfo *itemInfo, xmlNodePtr node);
+static void loadFloorSprite(SpriteDisplay *display, xmlNodePtr node);
+static int parseSpriteName(std::string &name);
+
+static char const *const fields[][2] =
+{
+ { "attack", N_("Attack %+d") },
+ { "defense", N_("Defense %+d") },
+ { "hp", N_("HP %+d") },
+ { "mp", N_("MP %+d") }
+};
+
+static std::list<ItemDB::Stat> extraStats;
+
+void ItemDB::setStatsList(const std::list<ItemDB::Stat> &stats)
+{
+ extraStats = stats;
+}
+
+static ItemType itemTypeFromString(const std::string &name)
+{
+ if (name == "generic")
+ {
+ return ITEM_UNUSABLE;
+ }
+ else if (name == "usable")
+ {
+ return ITEM_USABLE;
+ }
+ else if (name == "equip-1hand")
+ {
+ return ITEM_EQUIPMENT_ONE_HAND_WEAPON;
+ }
+ else if (name == "equip-2hand")
+ {
+ return ITEM_EQUIPMENT_TWO_HANDS_WEAPON;
+ }
+ else if (name == "equip-torso")
+ {
+ return ITEM_EQUIPMENT_TORSO;
+ }
+ else if (name == "equip-arms")
+ {
+ return ITEM_EQUIPMENT_ARMS;
+ }
+ else if (name == "equip-head")
+ {
+ return ITEM_EQUIPMENT_HEAD;
+ }
+ else if (name == "equip-legs")
+ {
+ return ITEM_EQUIPMENT_LEGS;
+ }
+ else if (name == "equip-shield")
+ {
+ return ITEM_EQUIPMENT_SHIELD;
+ }
+ else if (name == "equip-ring")
+ {
+ return ITEM_EQUIPMENT_RING;
+ }
+ else if (name == "equip-charm")
+ {
+ return ITEM_EQUIPMENT_CHARM;
+ }
+ else if (name == "equip-necklace" || name == "equip-neck")
+ {
+ return ITEM_EQUIPMENT_NECKLACE;
+ }
+ else if (name == "equip-feet")
+ {
+ return ITEM_EQUIPMENT_FEET;
+ }
+ else if (name == "equip-ammo")
+ {
+ return ITEM_EQUIPMENT_AMMO;
+ }
+ else if (name == "racesprite")
+ {
+ return ITEM_SPRITE_RACE;
+ }
+ else if (name == "hairsprite")
+ {
+ return ITEM_SPRITE_HAIR;
+ }
+ else
+ {
+ logger->log("Unknown item type: " + name);
+ return ITEM_UNUSABLE;
+ }
+}
+
+static std::string normalized(const std::string &name)
+{
+ std::string normalized = name;
+ return toLower(trim(normalized));
+}
+
+void ItemDB::load()
+{
+ if (mLoaded)
+ unload();
+
+ logger->log1("Initializing item database...");
+
+ mUnknown = new ItemInfo;
+ mUnknown->setName(_("Unknown item"));
+ mUnknown->setDisplay(SpriteDisplay());
+ std::string errFile = paths.getStringValue("spriteErrorFile");
+ mUnknown->setSprite(errFile, GENDER_MALE);
+ mUnknown->setSprite(errFile, GENDER_FEMALE);
+
+ XML::Document doc("items.xml");
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items"))
+ {
+ logger->log("ItemDB: Error while loading items.xml!");
+ mLoaded = true;
+ return;
+ }
+
+ for_each_xml_child_node(node, rootNode)
+ {
+ if (!xmlStrEqual(node->name, BAD_CAST "item"))
+ continue;
+
+ int id = XML::getProperty(node, "id", 0);
+
+ if (id == 0)
+ {
+ logger->log1("ItemDB: Invalid or missing item ID in items.xml!");
+ continue;
+ }
+ else if (mItemInfos.find(id) != mItemInfos.end())
+ {
+ logger->log("ItemDB: Redefinition of item ID %d", id);
+ }
+
+ std::string typeStr = XML::getProperty(node, "type", "other");
+ int weight = XML::getProperty(node, "weight", 0);
+ int view = XML::getProperty(node, "view", 0);
+
+ std::string name = XML::getProperty(node, "name", "");
+ std::string image = XML::getProperty(node, "image", "");
+ std::string description = XML::getProperty(node, "description", "");
+ std::string attackAction = XML::getProperty(node, "attack-action", "");
+ std::string drawBefore = XML::getProperty(node, "drawBefore", "");
+ std::string drawAfter = XML::getProperty(node, "drawAfter", "");
+
+ int drawPriority = XML::getProperty(node, "drawPriority", 0);
+
+ int attackRange = XML::getProperty(node, "attack-range", 0);
+ std::string missileParticle = XML::getProperty(
+ node, "missile-particle", "");
+
+ SpriteDisplay display;
+ display.image = image;
+
+ ItemInfo *itemInfo = new ItemInfo;
+ itemInfo->setId(id);
+ itemInfo->setName(name.empty() ? _("unnamed") : name);
+ itemInfo->setDescription(description);
+ itemInfo->setType(itemTypeFromString(typeStr));
+ itemInfo->setView(view);
+ itemInfo->setWeight(weight);
+ itemInfo->setAttackAction(attackAction);
+ itemInfo->setAttackRange(attackRange);
+ itemInfo->setMissileParticle(missileParticle);
+ itemInfo->setDrawBefore(parseSpriteName(drawBefore));
+ itemInfo->setDrawAfter(parseSpriteName(drawAfter));
+ itemInfo->setDrawPriority(drawPriority);
+
+ std::string effect;
+ for (int i = 0; i < int(sizeof(fields) / sizeof(fields[0])); ++i)
+ {
+ int value = XML::getProperty(node, fields[i][0], 0);
+ if (!value)
+ continue;
+ if (!effect.empty())
+ effect += " / ";
+ effect += strprintf(gettext(fields[i][1]), value);
+ }
+ for (std::list<Stat>::iterator it = extraStats.begin();
+ it != extraStats.end(); it++)
+ {
+ int value = XML::getProperty(node, it->tag.c_str(), 0);
+ if (!value)
+ continue;
+ if (!effect.empty())
+ effect += " / ";
+ effect += strprintf(it->format.c_str(), value);
+ }
+ std::string temp = XML::getProperty(node, "effect", "");
+ if (!effect.empty() && !temp.empty())
+ effect += " / ";
+ effect += temp;
+ itemInfo->setEffect(effect);
+
+ for_each_xml_child_node(itemChild, node)
+ {
+ if (xmlStrEqual(itemChild->name, BAD_CAST "sprite"))
+ {
+ std::string attackParticle = XML::getProperty(
+ itemChild, "particle-effect", "");
+ itemInfo->setParticleEffect(attackParticle);
+
+ loadSpriteRef(itemInfo, itemChild);
+ }
+ else if (xmlStrEqual(itemChild->name, BAD_CAST "sound"))
+ {
+ loadSoundRef(itemInfo, itemChild);
+ }
+ else if (xmlStrEqual(itemChild->name, BAD_CAST "floor"))
+ {
+ loadFloorSprite(&display, itemChild);
+ }
+ }
+
+ itemInfo->setDisplay(display);
+
+ mItemInfos[id] = itemInfo;
+ if (!name.empty())
+ {
+ std::string temp = normalized(name);
+
+ NamedItemInfos::const_iterator itr = mNamedItemInfos.find(temp);
+ if (itr == mNamedItemInfos.end())
+ {
+ mNamedItemInfos[temp] = itemInfo;
+ }
+ else
+ {
+ logger->log("ItemDB: Duplicate name of item found item %d",
+ id);
+ }
+ }
+
+ if (!attackAction.empty())
+ {
+ if (attackRange == 0)
+ {
+ logger->log("ItemDB: Missing attack range from weapon %i!",
+ id);
+ }
+ }
+
+#define CHECK_PARAM(param, error_value) \
+ if (param == error_value) \
+ logger->log("ItemDB: Missing " #param " attribute for item %i!", \
+ id)
+
+ if (id >= 0)
+ {
+ CHECK_PARAM(name, "");
+ CHECK_PARAM(description, "");
+ CHECK_PARAM(image, "");
+ }
+ // CHECK_PARAM(effect, "");
+ // CHECK_PARAM(type, 0);
+ // CHECK_PARAM(weight, 0);
+ // CHECK_PARAM(slot, 0);
+
+#undef CHECK_PARAM
+ }
+
+ mLoaded = true;
+}
+
+void ItemDB::unload()
+{
+ logger->log1("Unloading item database...");
+
+ delete mUnknown;
+ mUnknown = 0;
+
+ delete_all(mItemInfos);
+ mItemInfos.clear();
+ mNamedItemInfos.clear();
+ mLoaded = false;
+}
+
+bool ItemDB::exists(int id)
+{
+ assert(mLoaded);
+
+ ItemInfos::const_iterator i = mItemInfos.find(id);
+
+ return i != mItemInfos.end();
+}
+
+const ItemInfo &ItemDB::get(int id)
+{
+ assert(mLoaded);
+
+ ItemInfos::const_iterator i = mItemInfos.find(id);
+
+ if (i == mItemInfos.end())
+ {
+ logger->log("ItemDB: Warning, unknown item ID# %d", id);
+ return *mUnknown;
+ }
+
+ return *(i->second);
+}
+
+const ItemInfo &ItemDB::get(const std::string &name)
+{
+ assert(mLoaded);
+
+ NamedItemInfos::const_iterator i = mNamedItemInfos.find(normalized(name));
+
+ if (i == mNamedItemInfos.end())
+ {
+ if (!name.empty())
+ {
+ logger->log("ItemDB: Warning, unknown item name \"%s\"",
+ name.c_str());
+ }
+ return *mUnknown;
+ }
+
+ return *(i->second);
+}
+
+const std::map<int, ItemInfo*> &ItemDB::getItemInfos()
+{
+ return mItemInfos;
+}
+
+int parseSpriteName(std::string &name)
+{
+ int id = -1;
+ if (name == "shoes")
+ id = 1;
+ else if (name == "bottomclothes" || name == "bottom" || name == "pants")
+ id = 2;
+ else if (name == "topclothes" || name == "top" || name == "torso")
+ id = 3;
+ else if (name == "misc1")
+ id = 4;
+ else if (name == "misc2")
+ id = 5;
+ else if (name == "hair")
+ id = 6;
+ else if (name == "hat")
+ id = 7;
+ else if (name == "cap")
+ id = 8;
+ else if (name == "gloves")
+ id = 9;
+ else if (name == "weapon")
+ id = 10;
+ else if (name == "shield")
+ id = 11;
+
+ return id;
+}
+
+void loadSpriteRef(ItemInfo *itemInfo, xmlNodePtr node)
+{
+ std::string gender = XML::getProperty(node, "gender", "unisex");
+ std::string filename = (const char*) node->xmlChildrenNode->content;
+
+ if (gender == "male" || gender == "unisex")
+ {
+ itemInfo->setSprite(filename, GENDER_MALE);
+ }
+ if (gender == "female" || gender == "unisex")
+ {
+ itemInfo->setSprite(filename, GENDER_FEMALE);
+ }
+}
+
+void loadSoundRef(ItemInfo *itemInfo, xmlNodePtr node)
+{
+ std::string event = XML::getProperty(node, "event", "");
+ std::string filename = (const char*) node->xmlChildrenNode->content;
+
+ if (event == "hit")
+ {
+ itemInfo->addSound(EQUIP_EVENT_HIT, filename);
+ }
+ else if (event == "strike")
+ {
+ itemInfo->addSound(EQUIP_EVENT_STRIKE, filename);
+ }
+ else
+ {
+ logger->log("ItemDB: Ignoring unknown sound event '%s'",
+ event.c_str());
+ }
+}
+
+void loadFloorSprite(SpriteDisplay *display, xmlNodePtr floorNode)
+{
+ for_each_xml_child_node(spriteNode, floorNode)
+ {
+ if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite"))
+ {
+ SpriteReference *currentSprite = new SpriteReference;
+ currentSprite->sprite
+ = (const char*)spriteNode->xmlChildrenNode->content;
+ currentSprite->variant
+ = XML::getProperty(spriteNode, "variant", 0);
+ display->sprites.push_back(currentSprite);
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx"))
+ {
+ std::string particlefx
+ = (const char*)spriteNode->xmlChildrenNode->content;
+ display->particles.push_back(particlefx);
+ }
+ }
+}
diff --git a/src/resources/itemdb.h b/src/resources/itemdb.h
new file mode 100644
index 000000000..76e646c0f
--- /dev/null
+++ b/src/resources/itemdb.h
@@ -0,0 +1,79 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEM_MANAGER_H
+#define ITEM_MANAGER_H
+
+#include <list>
+#include <map>
+#include <string>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class ItemInfo;
+
+/**
+ * Item information database.
+ */
+namespace ItemDB
+{
+ /**
+ * Loads the item data from <code>items.xml</code>.
+ */
+ void load();
+
+ /**
+ * Frees item data.
+ */
+ void unload();
+
+ bool exists(int id);
+
+ const ItemInfo &get(int id);
+ const ItemInfo &get(const std::string &name);
+
+ // Items database
+ typedef std::map<int, ItemInfo*> ItemInfos;
+ typedef std::map<std::string, ItemInfo*> NamedItemInfos;
+
+ const std::map<int, ItemInfo*> &getItemInfos();
+
+ struct Stat
+ {
+ Stat(const std::string &tag,
+ const std::string &format):
+ tag(tag),
+ format(format)
+ {}
+
+ std::string tag;
+ std::string format;
+ };
+
+ void setStatsList(const std::list<Stat> &stats);
+
+}
+
+#endif
diff --git a/src/resources/iteminfo.cpp b/src/resources/iteminfo.cpp
new file mode 100644
index 000000000..300c6bd26
--- /dev/null
+++ b/src/resources/iteminfo.cpp
@@ -0,0 +1,66 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/iteminfo.h"
+
+#include "resources/itemdb.h"
+#include "configuration.h"
+
+const std::string &ItemInfo::getSprite(Gender gender) const
+{
+ if (mView)
+ {
+ // Forward the request to the item defining how to view this item
+ return ItemDB::get(mView).getSprite(gender);
+ }
+ else
+ {
+ static const std::string empty = "";
+ std::map<int, std::string>::const_iterator i =
+ mAnimationFiles.find(gender);
+
+ return (i != mAnimationFiles.end()) ? i->second : empty;
+ }
+}
+
+void ItemInfo::setAttackAction(std::string attackAction)
+{
+ if (attackAction.empty())
+ mAttackAction = SpriteAction::ATTACK; // (Equal to unarmed animation)
+ else
+ mAttackAction = attackAction;
+}
+
+void ItemInfo::addSound(EquipmentSoundEvent event, const std::string &filename)
+{
+ mSounds[event].push_back(paths.getStringValue("sfx") + filename);
+}
+
+const std::string &ItemInfo::getSound(EquipmentSoundEvent event) const
+{
+ static const std::string empty;
+ std::map< EquipmentSoundEvent,
+ std::vector<std::string> >::const_iterator i;
+
+ i = mSounds.find(event);
+
+ return i == mSounds.end() ? empty : i->second[rand() % i->second.size()];
+}
diff --git a/src/resources/iteminfo.h b/src/resources/iteminfo.h
new file mode 100644
index 000000000..bb84193bb
--- /dev/null
+++ b/src/resources/iteminfo.h
@@ -0,0 +1,242 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ITEMINFO_H
+#define ITEMINFO_H
+
+#include "being.h"
+
+#include "resources/spritedef.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+enum EquipmentSoundEvent
+{
+ EQUIP_EVENT_STRIKE = 0,
+ EQUIP_EVENT_HIT
+};
+
+enum EquipmentSlot
+{
+ // Equipment rules:
+ // 1 Brest equipment
+ EQUIP_TORSO_SLOT = 0,
+ // 1 arms equipment
+ EQUIP_ARMS_SLOT = 1,
+ // 1 head equipment
+ EQUIP_HEAD_SLOT = 2,
+ // 1 legs equipment
+ EQUIP_LEGS_SLOT = 3,
+ // 1 feet equipment
+ EQUIP_FEET_SLOT = 4,
+ // 2 rings
+ EQUIP_RING1_SLOT = 5,
+ EQUIP_RING2_SLOT = 6,
+ // 1 necklace
+ EQUIP_NECKLACE_SLOT = 7,
+ // Fight:
+ // 2 one-handed weapons
+ // or 1 two-handed weapon
+ // or 1 one-handed weapon + 1 shield.
+ EQUIP_FIGHT1_SLOT = 8,
+ EQUIP_FIGHT2_SLOT = 9,
+ // Projectile:
+ // this item does not amount to one, it only indicates the chosen projectile.
+ EQUIP_PROJECTILE_SLOT = 10
+};
+
+
+/**
+ * Enumeration of available Item types.
+ */
+enum ItemType
+{
+ ITEM_UNUSABLE = 0,
+ ITEM_USABLE,
+ ITEM_EQUIPMENT_ONE_HAND_WEAPON,
+ ITEM_EQUIPMENT_TWO_HANDS_WEAPON,
+ ITEM_EQUIPMENT_TORSO,
+ ITEM_EQUIPMENT_ARMS, // 5
+ ITEM_EQUIPMENT_HEAD,
+ ITEM_EQUIPMENT_LEGS,
+ ITEM_EQUIPMENT_SHIELD,
+ ITEM_EQUIPMENT_RING,
+ ITEM_EQUIPMENT_NECKLACE, // 10
+ ITEM_EQUIPMENT_FEET,
+ ITEM_EQUIPMENT_AMMO,
+ ITEM_EQUIPMENT_CHARM,
+ ITEM_SPRITE_RACE,
+ ITEM_SPRITE_HAIR // 15
+};
+
+/**
+ * Defines a class for storing item infos. This includes information used when
+ * the item is equipped.
+ */
+class ItemInfo
+{
+ public:
+ /**
+ * Constructor.
+ */
+ ItemInfo():
+ mType(ITEM_UNUSABLE),
+ mWeight(0),
+ mView(0),
+ mId(0),
+ mDrawBefore(-1),
+ mDrawAfter(-1),
+ mDrawPriority(0),
+ mAttackAction(SpriteAction::INVALID),
+ mAttackRange(0)
+ {
+ }
+
+ void setId(int id)
+ { mId = id; }
+
+ int getId() const
+ { return mId; }
+
+ void setName(const std::string &name)
+ { mName = name; }
+
+ const std::string &getName() const
+ { return mName; }
+
+ void setParticleEffect(const std::string &particleEffect)
+ { mParticle = particleEffect; }
+
+ std::string getParticleEffect() const { return mParticle; }
+
+ void setDisplay(SpriteDisplay display)
+ { mDisplay = display; }
+
+ const SpriteDisplay &getDisplay() const
+ { return mDisplay; }
+
+ void setDescription(const std::string &description)
+ { mDescription = description; }
+
+ const std::string &getDescription() const
+ { return mDescription; }
+
+ void setEffect(const std::string &effect)
+ { mEffect = effect; }
+
+ const std::string &getEffect() const { return mEffect; }
+
+ void setType(ItemType type)
+ { mType = type; }
+
+ ItemType getType() const
+ { return mType; }
+
+ void setWeight(int weight)
+ { mWeight = weight; }
+
+ int getWeight() const
+ { return mWeight; }
+
+ int getView() const
+ { return mView; }
+
+ void setView(int view)
+ { mView = view; }
+
+ void setSprite(const std::string &animationFile, Gender gender)
+ { mAnimationFiles[gender] = animationFile; }
+
+ const std::string &getSprite(Gender gender) const;
+
+ void setAttackAction(std::string attackAction);
+
+ // Handlers for seting and getting the string used for particles when attacking
+ void setMissileParticle(std::string s) { mMissileParticle = s; }
+
+ std::string getMissileParticle() const { return mMissileParticle; }
+
+ std::string getAttackAction() const
+ { return mAttackAction; }
+
+ int getAttackRange() const
+ { return mAttackRange; }
+
+ void setAttackRange(int r)
+ { mAttackRange = r; }
+
+ void addSound(EquipmentSoundEvent event, const std::string &filename);
+
+ const std::string &getSound(EquipmentSoundEvent event) const;
+
+ int getDrawBefore() const
+ { return mDrawBefore; }
+
+ void setDrawBefore(int n)
+ { mDrawBefore = n; }
+
+ int getDrawAfter() const
+ { return mDrawAfter; }
+
+ void setDrawAfter(int n)
+ { mDrawAfter = n; }
+
+ int getDrawPriority() const
+ { return mDrawPriority; }
+
+ void setDrawPriority(int n)
+ { mDrawPriority = n; }
+
+ protected:
+ SpriteDisplay mDisplay; /**< Display info (like icon) */
+ std::string mName;
+ std::string mDescription; /**< Short description. */
+ std::string mEffect; /**< Description of effects. */
+ ItemType mType; /**< Item type. */
+ std::string mParticle; /**< Particle effect used with this item */
+ int mWeight; /**< Weight in grams. */
+ int mView; /**< Item ID of how this item looks. */
+ int mId; /**< Item ID */
+ int mDrawBefore;
+ int mDrawAfter;
+ int mDrawPriority;
+
+ // Equipment related members.
+ /** Attack type, in case of weapon.
+ * See SpriteAction in spritedef.h for more info.
+ * Attack action sub-types (bow, sword, ...) are defined in items.xml.
+ */
+ std::string mAttackAction;
+ int mAttackRange; /**< Attack range, will be zero if non weapon. */
+
+ // Particle to be shown when weapon attacks
+ std::string mMissileParticle;
+
+ /** Maps gender to sprite filenames. */
+ std::map<int, std::string> mAnimationFiles;
+
+ /** Stores the names of sounds to be played at certain event. */
+ std::map< EquipmentSoundEvent, std::vector<std::string> > mSounds;
+};
+
+#endif
diff --git a/src/resources/mapreader.cpp b/src/resources/mapreader.cpp
new file mode 100644
index 000000000..cf26830f2
--- /dev/null
+++ b/src/resources/mapreader.cpp
@@ -0,0 +1,723 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/mapreader.h"
+
+#include "configuration.h"
+#include "log.h"
+#include "map.h"
+#include "tileset.h"
+
+#include "resources/animation.h"
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/base64.h"
+#include "utils/gettext.h"
+#include "utils/stringutils.h"
+#include "utils/xml.h"
+
+#include <cassert>
+#include <iostream>
+#include <zlib.h>
+
+int inflateMemory(unsigned char *in, unsigned int inLength,
+ unsigned char *&out, unsigned int &outLength);
+
+int inflateMemory(unsigned char *in, unsigned int inLength,
+ unsigned char *&out);
+
+static std::string resolveRelativePath(std::string base, std::string relative)
+{
+ // Remove trailing "/", if present
+ size_t i = base.length();
+ if (base.at(i - 1) == '/')
+ base.erase(i - 1, i);
+
+ while (relative.substr(0, 3) == "../")
+ {
+ relative.erase(0, 3); // Remove "../"
+ if (!base.empty()) // If base is already empty, we can't trim anymore
+ {
+ i = base.find_last_of('/');
+ if (i == std::string::npos)
+ i = 0;
+ base.erase(i, base.length()); // Remove deepest folder in base
+ }
+ }
+
+ // Re-add trailing slash, if needed
+ if (!base.empty() && base[base.length() - 1] != '/')
+ base += '/';
+
+ return base + relative;
+}
+
+/**
+ * Inflates either zlib or gzip deflated memory. The inflated memory is
+ * expected to be freed by the caller.
+ */
+int inflateMemory(unsigned char *in, unsigned int inLength,
+ unsigned char *&out, unsigned int &outLength)
+{
+ int bufferSize = 256 * 1024;
+ int ret;
+ z_stream strm;
+
+ out = (unsigned char*) malloc(bufferSize);
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.next_in = in;
+ strm.avail_in = inLength;
+ strm.next_out = out;
+ strm.avail_out = bufferSize;
+
+ ret = inflateInit2(&strm, 15 + 32);
+
+ if (ret != Z_OK)
+ return ret;
+
+ do
+ {
+ if (strm.next_out == NULL)
+ {
+ inflateEnd(&strm);
+ return Z_MEM_ERROR;
+ }
+
+ ret = inflate(&strm, Z_NO_FLUSH);
+ assert(ret != Z_STREAM_ERROR);
+
+ switch (ret)
+ {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ (void) inflateEnd(&strm);
+ return ret;
+ default:
+ break;
+ }
+
+ if (ret != Z_STREAM_END)
+ {
+ out = (unsigned char*) realloc(out, bufferSize * 2);
+
+ if (out == NULL)
+ {
+ inflateEnd(&strm);
+ return Z_MEM_ERROR;
+ }
+
+ strm.next_out = out + bufferSize;
+ strm.avail_out = bufferSize;
+ bufferSize *= 2;
+ }
+ }
+ while (ret != Z_STREAM_END);
+ assert(strm.avail_in == 0);
+
+ outLength = bufferSize - strm.avail_out;
+ (void) inflateEnd(&strm);
+ return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
+}
+
+int inflateMemory(unsigned char *in, unsigned int inLength,
+ unsigned char *&out)
+{
+ unsigned int outLength = 0;
+ int ret = inflateMemory(in, inLength, out, outLength);
+
+ if (ret != Z_OK || out == NULL)
+ {
+ if (ret == Z_MEM_ERROR)
+ {
+ logger->log1("Error: Out of memory while decompressing map data!");
+ }
+ else if (ret == Z_VERSION_ERROR)
+ {
+ logger->log1("Error: Incompatible zlib version!");
+ }
+ else if (ret == Z_DATA_ERROR)
+ {
+ logger->log1("Error: Incorrect zlib compressed data!");
+ }
+ else
+ {
+ logger->log1("Error: Unknown error while decompressing map data!");
+ }
+
+ free(out);
+ out = NULL;
+ outLength = 0;
+ }
+
+ return outLength;
+}
+
+Map *MapReader::readMap(const std::string &filename)
+{
+ logger->log("Attempting to read map %s", filename.c_str());
+ // Load the file through resource manager
+ ResourceManager *resman = ResourceManager::getInstance();
+ int fileSize;
+ void *buffer = resman->loadFile(filename, fileSize);
+ Map *map = NULL;
+
+ if (buffer == NULL)
+ {
+ logger->log("Map file not found (%s)", filename.c_str());
+ return NULL;
+ }
+
+ unsigned char *inflated;
+ unsigned int inflatedSize;
+
+ if (filename.find(".gz", filename.length() - 3) != std::string::npos)
+ {
+ // Inflate the gzipped map data
+ inflatedSize =
+ inflateMemory((unsigned char*) buffer, fileSize, inflated);
+ free(buffer);
+
+ if (inflated == NULL)
+ {
+ logger->log("Could not decompress map file (%s)",
+ filename.c_str());
+ return NULL;
+ }
+ }
+ else
+ {
+ inflated = (unsigned char*) buffer;
+ inflatedSize = fileSize;
+ }
+
+ XML::Document doc((char*) inflated, inflatedSize);
+ free(inflated);
+
+ xmlNodePtr node = doc.rootNode();
+
+ // Parse the inflated map data
+ if (node)
+ {
+ if (!xmlStrEqual(node->name, BAD_CAST "map"))
+ {
+ logger->log("Error: Not a map file (%s)!", filename.c_str());
+ }
+ else
+ {
+ map = readMap(node, filename);
+ }
+ }
+ else
+ {
+ logger->log("Error while parsing map file (%s)!", filename.c_str());
+ }
+
+ if (map) map->setProperty("_filename", filename);
+
+ return map;
+}
+
+Map *MapReader::readMap(xmlNodePtr node, const std::string &path)
+{
+ // Take the filename off the path
+ const std::string pathDir = path.substr(0, path.rfind("/") + 1);
+
+ const int w = XML::getProperty(node, "width", 0);
+ const int h = XML::getProperty(node, "height", 0);
+ const int tilew = XML::getProperty(node, "tilewidth", -1);
+ const int tileh = XML::getProperty(node, "tileheight", -1);
+
+ bool showWarps = config.getBoolValue("warpParticle");
+ const std::string warpPath = paths.getStringValue("particles")
+ + paths.getStringValue("portalEffectFile");
+
+ if (tilew < 0 || tileh < 0)
+ {
+ logger->log("MapReader: Warning: "
+ "Unitialized tile width or height value for map: %s",
+ path.c_str());
+ return 0;
+ }
+
+ Map *map = new Map(w, h, tilew, tileh);
+
+ for_each_xml_child_node(childNode, node)
+ {
+ if (xmlStrEqual(childNode->name, BAD_CAST "tileset"))
+ {
+ Tileset *tileset = readTileset(childNode, pathDir, map);
+ if (tileset)
+ {
+ map->addTileset(tileset);
+ }
+ }
+ else if (xmlStrEqual(childNode->name, BAD_CAST "layer"))
+ {
+ readLayer(childNode, map);
+ }
+ else if (xmlStrEqual(childNode->name, BAD_CAST "properties"))
+ {
+ readProperties(childNode, map);
+ }
+ else if (xmlStrEqual(childNode->name, BAD_CAST "objectgroup"))
+ {
+ // The object group offset is applied to each object individually
+ const int tileOffsetX = XML::getProperty(childNode, "x", 0);
+ const int tileOffsetY = XML::getProperty(childNode, "y", 0);
+ const int offsetX = tileOffsetX * tilew;
+ const int offsetY = tileOffsetY * tileh;
+
+ for_each_xml_child_node(objectNode, childNode)
+ {
+ if (xmlStrEqual(objectNode->name, BAD_CAST "object"))
+ {
+ std::string objType = XML::getProperty(
+ objectNode, "type", "");
+
+ objType = toUpper(objType);
+
+/*
+ if (objType == "NPC" ||
+ objType == "SCRIPT")
+ {
+ logger->log("hidden obj: " + objType);
+ // Silently skip server-side objects.
+ continue;
+ }
+*/
+
+ const std::string objName = XML::getProperty(
+ objectNode, "name", "");
+ const int objX = XML::getProperty(objectNode, "x", 0);
+ const int objY = XML::getProperty(objectNode, "y", 0);
+ const int objW = XML::getProperty(objectNode, "width", 0);
+ const int objH = XML::getProperty(objectNode, "height", 0);
+
+ logger->log("- Loading object name: %s type: %s at %d:%d"
+ " (%dx%d)", objName.c_str(), objType.c_str(),
+ objX, objY, objW, objH);
+
+ if (objType == "PARTICLE_EFFECT")
+ {
+ if (objName.empty())
+ {
+ logger->log1(" Warning: No particle file given");
+ continue;
+ }
+
+ map->addParticleEffect(objName,
+ objX + offsetX,
+ objY + offsetY,
+ objW, objH);
+ }
+ else if (objType == "WARP")
+ {
+ if (showWarps)
+ {
+ map->addParticleEffect(warpPath,
+ objX, objY, objW, objH);
+ }
+ map->addPortal(objName, MapItem::PORTAL,
+ objX, objY, objW, objH);
+ }
+ else if (objType == "SPAWN")
+ {
+// map->addPortal(_("Spawn: ") + objName, MapItem::PORTAL,
+// objX, objY, objW, objH);
+ }
+ else
+ {
+ logger->log1(" Warning: Unknown object type");
+ }
+ }
+ }
+ }
+ }
+
+ map->initializeAmbientLayers();
+
+ return map;
+}
+
+void MapReader::readProperties(xmlNodePtr node, Properties *props)
+{
+ if (!props)
+ return;
+
+ for_each_xml_child_node(childNode, node)
+ {
+ if (!xmlStrEqual(childNode->name, BAD_CAST "property"))
+ continue;
+
+ // Example: <property name="name" value="value"/>
+ const std::string name = XML::getProperty(childNode, "name", "");
+ const std::string value = XML::getProperty(childNode, "value", "");
+
+ if (!name.empty() && !value.empty())
+ props->setProperty(name, value);
+ }
+}
+
+static void setTile(Map *map, MapLayer *layer, int x, int y, int gid)
+{
+ const Tileset * const set = map->getTilesetWithGid(gid);
+ if (layer)
+ {
+ // Set regular tile on a layer
+ Image * const img = set ? set->get(gid - set->getFirstGid()) : 0;
+ layer->setTile(x, y, img);
+ }
+ else
+ {
+ // Set collision tile
+// if (set && (gid - set->getFirstGid() == 1)) buggy update
+ if (set && (gid - set->getFirstGid() != 0))
+ map->blockTile(x, y, Map::BLOCKTYPE_WALL);
+ }
+}
+
+void MapReader::readLayer(xmlNodePtr node, Map *map)
+{
+ // Layers are not necessarily the same size as the map
+ const int w = XML::getProperty(node, "width", map->getWidth());
+ const int h = XML::getProperty(node, "height", map->getHeight());
+ const int offsetX = XML::getProperty(node, "x", 0);
+ const int offsetY = XML::getProperty(node, "y", 0);
+ std::string name = XML::getProperty(node, "name", "");
+ name = toLower(name);
+
+ const bool isFringeLayer = (name.substr(0, 6) == "fringe");
+ const bool isCollisionLayer = (name.substr(0, 9) == "collision");
+
+ MapLayer *layer = 0;
+
+ if (!isCollisionLayer)
+ {
+ layer = new MapLayer(offsetX, offsetY, w, h, isFringeLayer);
+ map->addLayer(layer);
+ }
+
+ logger->log("- Loading layer \"%s\"", name.c_str());
+ int x = 0;
+ int y = 0;
+
+ // Load the tile data
+ for_each_xml_child_node(childNode, node)
+ {
+ if (!xmlStrEqual(childNode->name, BAD_CAST "data"))
+ continue;
+
+ const std::string encoding =
+ XML::getProperty(childNode, "encoding", "");
+ const std::string compression =
+ XML::getProperty(childNode, "compression", "");
+
+ if (encoding == "base64")
+ {
+ if (!compression.empty() && compression != "gzip")
+ {
+ logger->log1("Warning: only gzip layer"
+ " compression supported!");
+ return;
+ }
+
+ // Read base64 encoded map file
+ xmlNodePtr dataChild = childNode->xmlChildrenNode;
+ if (!dataChild)
+ continue;
+
+ int len = static_cast<int>(strlen(
+ (const char*)dataChild->content) + 1);
+ unsigned char *charData = new unsigned char[len + 1];
+ const char *charStart = (const char*)dataChild->content;
+ unsigned char *charIndex = charData;
+
+ while (*charStart)
+ {
+ if (*charStart != ' ' && *charStart != '\t' &&
+ *charStart != '\n')
+ {
+ *charIndex = *charStart;
+ charIndex++;
+ }
+ charStart++;
+ }
+ *charIndex = '\0';
+
+ int binLen;
+ unsigned char *binData = php3_base64_decode(charData,
+ static_cast<int>(strlen((char*)charData)), &binLen);
+
+ delete[] charData;
+
+ if (binData)
+ {
+ if (compression == "gzip")
+ {
+ // Inflate the gzipped layer data
+ unsigned char *inflated;
+ unsigned int inflatedSize =
+ inflateMemory(binData, binLen, inflated);
+
+ free(binData);
+ binData = inflated;
+ binLen = inflatedSize;
+
+ if (!inflated)
+ {
+ logger->log1("Error: Could not decompress layer!");
+ return;
+ }
+ }
+
+ for (int i = 0; i < binLen - 3; i += 4)
+ {
+ const int gid = binData[i] |
+ binData[i + 1] << 8 |
+ binData[i + 2] << 16 |
+ binData[i + 3] << 24;
+
+ setTile(map, layer, x, y, gid);
+
+ TileAnimation* ani = map->getAnimationForGid(gid);
+ if (ani)
+ ani->addAffectedTile(layer, x + y * w);
+
+ x++;
+ if (x == w)
+ {
+ x = 0; y++;
+
+ // When we're done, don't crash on too much data
+ if (y == h)
+ break;
+ }
+ }
+ free(binData);
+ }
+ }
+ else
+ {
+ // Read plain XML map file
+ for_each_xml_child_node(childNode2, childNode)
+ {
+ if (!xmlStrEqual(childNode2->name, BAD_CAST "tile"))
+ continue;
+
+ const int gid = XML::getProperty(childNode2, "gid", -1);
+ setTile(map, layer, x, y, gid);
+
+ x++;
+ if (x == w)
+ {
+ x = 0; y++;
+ if (y >= h)
+ break;
+ }
+ }
+ }
+
+ if (y < h)
+ std::cerr << "TOO SMALL!\n";
+ if (x)
+ std::cerr << "TOO SMALL!\n";
+
+ // There can be only one data element
+ break;
+ }
+
+/*
+ if (!layer)
+ return;
+
+ for (int y = 0; y < layer->getHeight(); y ++)
+ {
+ for (int x = 0 ; x < layer->getWidth() ; x ++)
+ {
+ int width;
+ int c = layer->getTileDrawWidth(x, y, layer->getWidth(), width);
+ layer->setTileInfo(x, y, width, c);
+ }
+ }
+*/
+
+/*
+ Image *img1 = 0;
+ for (int y = 0; y < layer->getHeight(); y ++)
+ {
+ int skipWidth = 0;
+ int skipCount = 0;
+ img1 = layer->getTile(0, y);
+ layer->setTileInfo(layer->getWidth() - 1, y, skipWidth, skipCount);
+ for (int x = layer->getWidth() - 1 ; x > 0 ; x --)
+ {
+ Image *img = layer->getTile(x, y);
+ if (img)
+ {
+ if (img != img1)
+ { // different tile
+ skipWidth = 0;
+ skipCount = 0;
+ }
+ else
+ { // same tile
+ skipWidth += img1->getWidth();
+ skipCount ++;
+ }
+ }
+ else
+ {
+ skipWidth = 0;
+ skipCount = 0;
+ }
+ layer->setTileInfo(x, y, skipWidth, skipCount);
+ img1 = img;
+ }
+ }
+*/
+}
+
+Tileset *MapReader::readTileset(xmlNodePtr node, const std::string &path,
+ Map *map)
+{
+ if (!map)
+ return NULL;
+
+ int firstGid = XML::getProperty(node, "firstgid", 0);
+ int margin = XML::getProperty(node, "margin", 0);
+ int spacing = XML::getProperty(node, "spacing", 0);
+ XML::Document* doc = NULL;
+ Tileset *set = NULL;
+ std::string pathDir(path);
+
+ if (xmlHasProp(node, BAD_CAST "source"))
+ {
+ std::string filename = XML::getProperty(node, "source", "");
+ filename = resolveRelativePath(path, filename);
+
+ doc = new XML::Document(filename);
+ node = doc->rootNode();
+
+ // Reset path to be realtive to the tsx file
+ pathDir = filename.substr(0, filename.rfind("/") + 1);
+ }
+
+ const int tw = XML::getProperty(node, "tilewidth", map->getTileWidth());
+ const int th = XML::getProperty(node, "tileheight", map->getTileHeight());
+
+ for_each_xml_child_node(childNode, node)
+ {
+ if (xmlStrEqual(childNode->name, BAD_CAST "image"))
+ {
+ const std::string source = XML::getProperty(
+ childNode, "source", "");
+
+ if (!source.empty())
+ {
+ std::string sourceStr = resolveRelativePath(pathDir, source);
+
+ ResourceManager *resman = ResourceManager::getInstance();
+ Image* tilebmp = resman->getImage(sourceStr);
+
+ if (tilebmp)
+ {
+ set = new Tileset(tilebmp, tw, th, firstGid, margin,
+ spacing);
+ tilebmp->decRef();
+ }
+ else
+ {
+ logger->log("Warning: Failed to load tileset (%s)",
+ source.c_str());
+ }
+ }
+ }
+ else if (xmlStrEqual(childNode->name, BAD_CAST "tile"))
+ {
+ for_each_xml_child_node(tileNode, childNode)
+ {
+ if (!xmlStrEqual(tileNode->name, BAD_CAST "properties"))
+ continue;
+
+ int tileGID = firstGid + XML::getProperty(childNode, "id", 0);
+
+ // read tile properties to a map for simpler handling
+ std::map<std::string, int> tileProperties;
+ for_each_xml_child_node(propertyNode, tileNode)
+ {
+ if (!xmlStrEqual(propertyNode->name, BAD_CAST "property"))
+ continue;
+ std::string name = XML::getProperty(
+ propertyNode, "name", "");
+ int value = XML::getProperty(propertyNode, "value", 0);
+ tileProperties[name] = value;
+ logger->log("Tile Prop of %d \"%s\" = \"%d\"",
+ tileGID, name.c_str(), value);
+ }
+
+ // create animation
+ if (!set)
+ continue;
+
+ Animation *ani = new Animation;
+ for (int i = 0; ; i++)
+ {
+ std::map<std::string, int>::iterator iFrame, iDelay;
+ iFrame = tileProperties.find(
+ "animation-frame" + toString(i));
+ iDelay = tileProperties.find(
+ "animation-delay" + toString(i));
+ if (iFrame != tileProperties.end()
+ && iDelay != tileProperties.end())
+ {
+ ani->addFrame(set->get(iFrame->second),
+ iDelay->second, 0, 0);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (ani->getLength() > 0)
+ {
+ map->addAnimation(tileGID, new TileAnimation(ani));
+ logger->log("Animation length: %d", ani->getLength());
+ }
+ else
+ {
+ delete ani;
+ ani = 0;
+ }
+ }
+ }
+ }
+
+ delete doc;
+
+ return set;
+}
diff --git a/src/resources/mapreader.h b/src/resources/mapreader.h
new file mode 100644
index 000000000..ffa69d838
--- /dev/null
+++ b/src/resources/mapreader.h
@@ -0,0 +1,78 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MAPREADER_H
+#define MAPREADER_H
+
+#include <libxml/tree.h>
+
+#include <string>
+
+#ifdef __GNUC__
+#define _UNUSED_ __attribute__ ((unused))
+#else
+#define _UNUSED_
+#endif
+
+class Map;
+class Properties;
+class Tileset;
+
+/**
+ * Reader for XML map files (*.tmx)
+ */
+class MapReader
+{
+ public:
+ /**
+ * Read an XML map from a file.
+ */
+ static Map *readMap(const std::string &filename);
+
+ /**
+ * Read an XML map from a parsed XML tree. The path is used to find the
+ * location of referenced tileset images.
+ */
+ static Map *readMap(xmlNodePtr node, const std::string &path);
+
+ private:
+ /**
+ * Reads the properties element.
+ *
+ * @param node The <code>properties</code> element.
+ * @param props The Properties instance to which the properties will
+ * be assigned.
+ */
+ static void readProperties(xmlNodePtr node, Properties* props);
+
+ /**
+ * Reads a map layer and adds it to the given map.
+ */
+ static void readLayer(xmlNodePtr node, Map *map);
+
+ /**
+ * Reads a tile set.
+ */
+ static Tileset *readTileset(xmlNodePtr node, const std::string &path,
+ Map *map);
+};
+
+#endif
diff --git a/src/resources/monsterdb.cpp b/src/resources/monsterdb.cpp
new file mode 100644
index 000000000..badd123c7
--- /dev/null
+++ b/src/resources/monsterdb.cpp
@@ -0,0 +1,199 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/monsterdb.h"
+
+#include "log.h"
+
+#include "net/net.h"
+
+#include "resources/beinginfo.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+#include "utils/xml.h"
+
+#include "configuration.h"
+
+#define OLD_TMWATHENA_OFFSET 1002
+
+namespace
+{
+ BeingInfos mMonsterInfos;
+ bool mLoaded = false;
+}
+
+void MonsterDB::load()
+{
+ if (mLoaded)
+ unload();
+
+ logger->log1("Initializing monster database...");
+
+ XML::Document doc("monsters.xml");
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "monsters"))
+ {
+ logger->log("Monster Database: Error while loading monster.xml!");
+ mLoaded = true;
+ return;
+ }
+
+ int offset = XML::getProperty(rootNode, "offset", Net::getNetworkType() ==
+ ServerInfo::TMWATHENA ? OLD_TMWATHENA_OFFSET : 0);
+
+ //iterate <monster>s
+ for_each_xml_child_node(monsterNode, rootNode)
+ {
+ if (!xmlStrEqual(monsterNode->name, BAD_CAST "monster"))
+ continue;
+
+ BeingInfo *currentInfo = new BeingInfo;
+
+ currentInfo->setWalkMask(Map::BLOCKMASK_WALL
+ | Map::BLOCKMASK_CHARACTER
+ | Map::BLOCKMASK_MONSTER);
+ currentInfo->setBlockType(Map::BLOCKTYPE_MONSTER);
+
+ currentInfo->setName(XML::getProperty(
+ monsterNode, "name", _("unnamed")));
+
+ currentInfo->setTargetCursorSize(XML::getProperty(monsterNode,
+ "targetCursor", "medium"));
+
+ currentInfo->setTargetOffsetX(XML::getProperty(monsterNode,
+ "targetOffsetX", 0));
+
+ currentInfo->setTargetOffsetY(XML::getProperty(monsterNode,
+ "targetOffsetY", 0));
+
+ currentInfo->setMaxHP(XML::getProperty(monsterNode,
+ "maxHP", 0));
+
+ if (currentInfo->getMaxHP())
+ currentInfo->setStaticMaxHP(true);
+
+ SpriteDisplay display;
+
+ //iterate <sprite>s and <sound>s
+ for_each_xml_child_node(spriteNode, monsterNode)
+ {
+ if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite"))
+ {
+ SpriteReference *currentSprite = new SpriteReference;
+ currentSprite->sprite
+ = (const char*)spriteNode->xmlChildrenNode->content;
+
+ currentSprite->variant = XML::getProperty(
+ spriteNode, "variant", 0);
+ display.sprites.push_back(currentSprite);
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "sound"))
+ {
+ std::string event = XML::getProperty(spriteNode, "event", "");
+ const char *filename;
+ filename = (const char*) spriteNode->xmlChildrenNode->content;
+
+ if (event == "hit")
+ {
+ currentInfo->addSound(SOUND_EVENT_HIT, filename);
+ }
+ else if (event == "miss")
+ {
+ currentInfo->addSound(SOUND_EVENT_MISS, filename);
+ }
+ else if (event == "hurt")
+ {
+ currentInfo->addSound(SOUND_EVENT_HURT, filename);
+ }
+ else if (event == "die")
+ {
+ currentInfo->addSound(SOUND_EVENT_DIE, filename);
+ }
+ else
+ {
+ logger->log("MonsterDB: Warning, sound effect %s for "
+ "unknown event %s of monster %s",
+ filename, event.c_str(),
+ currentInfo->getName().c_str());
+ }
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "attack"))
+ {
+ const int id = XML::getProperty(spriteNode, "id", 0);
+ const std::string particleEffect = XML::getProperty(
+ spriteNode, "particle-effect", "");
+ const std::string spriteAction = XML::getProperty(spriteNode,
+ "action",
+ "attack");
+ const std::string missileParticle = XML::getProperty(
+ spriteNode, "missile-particle", "");
+ currentInfo->addAttack(id, spriteAction,
+ particleEffect, missileParticle);
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx"))
+ {
+ display.particles.push_back(
+ (const char*) spriteNode->xmlChildrenNode->content);
+ }
+ }
+ currentInfo->setDisplay(display);
+
+ mMonsterInfos[XML::getProperty(
+ monsterNode, "id", 0) + offset] = currentInfo;
+ }
+
+ mLoaded = true;
+}
+
+void MonsterDB::unload()
+{
+ delete_all(mMonsterInfos);
+ mMonsterInfos.clear();
+
+ mLoaded = false;
+}
+
+
+BeingInfo *MonsterDB::get(int id)
+{
+ BeingInfoIterator i = mMonsterInfos.find(id);
+
+ if (i == mMonsterInfos.end())
+ {
+ i = mMonsterInfos.find(id + /*1002*/ OLD_TMWATHENA_OFFSET);
+ if (i == mMonsterInfos.end())
+ {
+ logger->log("MonsterDB: Warning, unknown monster ID %d requested",
+ id);
+ return BeingInfo::Unknown;
+ }
+ else
+ {
+ return i->second;
+ }
+ }
+ else
+ {
+ return i->second;
+ }
+}
diff --git a/src/resources/monsterdb.h b/src/resources/monsterdb.h
new file mode 100644
index 000000000..50f704388
--- /dev/null
+++ b/src/resources/monsterdb.h
@@ -0,0 +1,39 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MONSTER_DB_H
+#define MONSTER_DB_H
+
+class BeingInfo;
+
+/**
+ * Monster information database.
+ */
+namespace MonsterDB
+{
+ void load();
+
+ void unload();
+
+ BeingInfo *get(int id);
+}
+
+#endif
diff --git a/src/resources/music.cpp b/src/resources/music.cpp
new file mode 100644
index 000000000..c939d4f79
--- /dev/null
+++ b/src/resources/music.cpp
@@ -0,0 +1,84 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/music.h"
+
+#include "log.h"
+
+Music::Music(Mix_Chunk *music):
+ mChunk(music),
+ mChannel(-1)
+{
+}
+
+Music::~Music()
+{
+ //Mix_FreeMusic(music);
+ Mix_FreeChunk(mChunk);
+}
+
+Resource *Music::load(void *buffer, unsigned bufferSize)
+{
+ // Load the raw file data from the buffer in an RWops structure
+ SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize);
+
+ // Use Mix_LoadMUS to load the raw music data
+ //Mix_Music* music = Mix_LoadMUS_RW(rw); Need to be implemeted
+ Mix_Chunk *tmpMusic = Mix_LoadWAV_RW(rw, 1);
+
+ if (tmpMusic)
+ {
+ return new Music(tmpMusic);
+ }
+ else
+ {
+ logger->log("Error, failed to load music: %s", Mix_GetError());
+ return NULL;
+ }
+}
+
+bool Music::play(int loops)
+{
+ /*
+ * Warning: loops should be always set to -1 (infinite) with current
+ * implementation to avoid halting the playback of other samples
+ */
+
+ /*if (Mix_PlayMusic(music, loops))
+ return true;*/
+ Mix_VolumeChunk(mChunk, 120);
+ mChannel = Mix_PlayChannel(-1, mChunk, loops);
+
+ return mChannel != -1;
+}
+
+void Music::stop()
+{
+ /*
+ * Warning: very dungerous trick, it could try to stop channels occupied
+ * by samples rather than the current music file
+ */
+
+ //Mix_HaltMusic();
+
+ if (mChannel != -1)
+ Mix_HaltChannel(mChannel);
+}
diff --git a/src/resources/music.h b/src/resources/music.h
new file mode 100644
index 000000000..c0cf5abe9
--- /dev/null
+++ b/src/resources/music.h
@@ -0,0 +1,81 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MUSIC_H
+#define MUSIC_H
+
+#include "resources/resource.h"
+
+#ifdef __APPLE__
+#include <SDL_mixer/SDL_mixer.h>
+#else
+#include <SDL_mixer.h>
+#endif
+
+/**
+ * Defines a class for loading and storing music.
+ */
+class Music : public Resource
+{
+ public:
+ /**
+ * Destructor.
+ */
+ virtual ~Music();
+
+ /**
+ * Loads a music from a buffer in memory.
+ *
+ * @param buffer The memory buffer containing the music data.
+ * @param bufferSize The size of the memory buffer in bytes.
+ *
+ * @return <code>NULL</code> if the an error occurred, a valid pointer
+ * otherwise.
+ */
+ static Resource *load(void *buffer, unsigned bufferSize);
+
+ /**
+ * Plays the music.
+ *
+ * @param loops Number of times to repeat the playback.
+ *
+ * @return <code>true</code> if the playback started properly
+ * <code>false</code> otherwise.
+ */
+ virtual bool play(int loops);
+
+ /**
+ * Stops the music.
+ */
+ virtual void stop();
+
+ protected:
+ /**
+ * Constructor.
+ */
+ Music(Mix_Chunk *music);
+
+ //Mix_Music *music;
+ Mix_Chunk *mChunk;
+ int mChannel;
+};
+
+#endif
diff --git a/src/resources/npcdb.cpp b/src/resources/npcdb.cpp
new file mode 100644
index 000000000..a5b9298b0
--- /dev/null
+++ b/src/resources/npcdb.cpp
@@ -0,0 +1,127 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/npcdb.h"
+
+#include "log.h"
+
+#include "resources/beinginfo.h"
+
+#include "utils/dtor.h"
+#include "utils/xml.h"
+#include "configuration.h"
+
+namespace
+{
+ BeingInfos mNPCInfos;
+ bool mLoaded = false;
+}
+
+void NPCDB::load()
+{
+ if (mLoaded)
+ unload();
+
+ logger->log1("Initializing NPC database...");
+
+ XML::Document doc("npcs.xml");
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "npcs"))
+ {
+ logger->log("NPC Database: Error while loading npcs.xml!");
+ mLoaded = true;
+ return;
+ }
+
+ //iterate <npc>s
+ for_each_xml_child_node(npcNode, rootNode)
+ {
+ if (!xmlStrEqual(npcNode->name, BAD_CAST "npc"))
+ continue;
+
+ int id = XML::getProperty(npcNode, "id", 0);
+ if (id == 0)
+ {
+ logger->log1("NPC Database: NPC with missing ID in npcs.xml!");
+ continue;
+ }
+
+ BeingInfo *currentInfo = new BeingInfo;
+
+ currentInfo->setTargetCursorSize(XML::getProperty(npcNode,
+ "targetCursor", "medium"));
+
+ currentInfo->setTargetOffsetX(XML::getProperty(npcNode,
+ "targetOffsetX", 0));
+
+ currentInfo->setTargetOffsetY(XML::getProperty(npcNode,
+ "targetOffsetY", 0));
+ SpriteDisplay display;
+ for_each_xml_child_node(spriteNode, npcNode)
+ {
+ if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite"))
+ {
+ SpriteReference *currentSprite = new SpriteReference;
+ currentSprite->sprite =
+ (const char*)spriteNode->xmlChildrenNode->content;
+ currentSprite->variant =
+ XML::getProperty(spriteNode, "variant", 0);
+ display.sprites.push_back(currentSprite);
+ }
+ else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx"))
+ {
+ std::string particlefx =
+ (const char*)spriteNode->xmlChildrenNode->content;
+ display.particles.push_back(particlefx);
+ }
+ }
+
+ currentInfo->setDisplay(display);
+
+ mNPCInfos[id] = currentInfo;
+ }
+
+ mLoaded = true;
+}
+
+void NPCDB::unload()
+{
+ delete_all(mNPCInfos);
+ mNPCInfos.clear();
+
+ mLoaded = false;
+}
+
+BeingInfo *NPCDB::get(int id)
+{
+ BeingInfoIterator i = mNPCInfos.find(id);
+
+ if (i == mNPCInfos.end())
+ {
+ logger->log("NPCDB: Warning, unknown NPC ID %d requested", id);
+ return BeingInfo::Unknown;
+ }
+ else
+ {
+ return i->second;
+ }
+}
diff --git a/src/resources/npcdb.h b/src/resources/npcdb.h
new file mode 100644
index 000000000..b0c89c804
--- /dev/null
+++ b/src/resources/npcdb.h
@@ -0,0 +1,39 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NPC_DB_H
+#define NPC_DB_H
+
+class BeingInfo;
+
+/**
+ * NPC information database.
+ */
+namespace NPCDB
+{
+ void load();
+
+ void unload();
+
+ BeingInfo *get(int id);
+}
+
+#endif
diff --git a/src/resources/resource.cpp b/src/resources/resource.cpp
new file mode 100644
index 000000000..5a8dd3176
--- /dev/null
+++ b/src/resources/resource.cpp
@@ -0,0 +1,58 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/resource.h"
+
+#include "client.h"
+#include "log.h"
+
+#include "resources/resourcemanager.h"
+
+#include <cassert>
+
+Resource::~Resource()
+{
+}
+
+void Resource::incRef()
+{
+ mRefCount++;
+}
+
+void Resource::decRef()
+{
+ // Reference may not already have reached zero
+ if (mRefCount == 0)
+ {
+ logger->log("Warning: mRefCount already zero for %s", mIdPath.c_str());
+ return;
+// assert(false);
+ }
+
+ mRefCount--;
+
+ if (mRefCount == 0)
+ {
+ // Warn the manager that this resource is no longer used.
+ ResourceManager *resman = ResourceManager::getInstance();
+ resman->release(this);
+ }
+}
diff --git a/src/resources/resource.h b/src/resources/resource.h
new file mode 100644
index 000000000..89fcc9de7
--- /dev/null
+++ b/src/resources/resource.h
@@ -0,0 +1,81 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RESOURCE_H
+#define RESOURCE_H
+
+#include <ctime>
+#include <string>
+
+/**
+ * A generic reference counted resource object.
+ */
+class Resource
+{
+ friend class ResourceManager;
+
+ public:
+ /**
+ * Constructor
+ */
+ Resource(): mRefCount(0)
+ { }
+
+ /**
+ * Increments the internal reference count.
+ */
+ void incRef();
+
+ /**
+ * Decrements the reference count and deletes the object
+ * if no references are left.
+ *
+ * @return <code>true</code> if the object was deleted
+ * <code>false</code> otherwise.
+ */
+ void decRef();
+
+ /**
+ * Return the path identifying this resource.
+ */
+ const std::string &getIdPath() const
+ { return mIdPath; }
+
+ /**
+ * Return refCount for this resource.
+ */
+ unsigned getRefCount()
+ { return mRefCount; }
+
+ protected:
+ /**
+ * Destructor.
+ */
+ virtual ~Resource();
+
+ private:
+ std::string mIdPath; /**< Path identifying this resource. */
+ time_t mTimeStamp; /**< Time at which the resource was orphaned. */
+ unsigned mRefCount; /**< Reference count. */
+ std::string mName;
+};
+
+#endif
diff --git a/src/resources/resourcemanager.cpp b/src/resources/resourcemanager.cpp
new file mode 100644
index 000000000..7bdcce386
--- /dev/null
+++ b/src/resources/resourcemanager.cpp
@@ -0,0 +1,658 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/resourcemanager.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "log.h"
+#include "main.h"
+
+#include "resources/dye.h"
+#include "resources/image.h"
+#include "resources/imageset.h"
+#include "resources/music.h"
+#include "resources/soundeffect.h"
+#include "resources/spritedef.h"
+
+#include <physfs.h>
+#include <SDL_image.h>
+#include <cassert>
+#include <sstream>
+
+#include <sys/time.h>
+
+#define THEMES_FOLDER "themes"
+
+ResourceManager *ResourceManager::instance = NULL;
+
+ResourceManager::ResourceManager()
+ : mOldestOrphan(0),
+ mSelectedSkin(""),
+ mSkinName("")
+{
+ logger->log1("Initializing resource manager...");
+}
+
+ResourceManager::~ResourceManager()
+{
+ mResources.insert(mOrphanedResources.begin(), mOrphanedResources.end());
+
+ // Release any remaining spritedefs first because they depend on image sets
+ ResourceIterator iter = mResources.begin();
+
+#ifdef DEBUG_LEAKS
+ while (iter != mResources.end())
+ {
+ if (iter->second)
+ {
+ if (iter->second->getRefCount())
+ {
+ logger->log("ResourceLeak: " + iter->second->getIdPath()
+ + " (" + toString(iter->second->getRefCount())
+ + ")");
+ }
+ }
+ ++iter;
+ }
+
+ iter = mResources.begin();
+#endif
+
+ while (iter != mResources.end())
+ {
+#ifdef DEBUG_LEAKS
+ if (iter->second && iter->second->getRefCount())
+ {
+ ++iter;
+ continue;
+ }
+#endif
+ if (dynamic_cast<SpriteDef*>(iter->second) != 0)
+ {
+ cleanUp(iter->second);
+ ResourceIterator toErase = iter;
+ ++iter;
+ mResources.erase(toErase);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+
+ // Release any remaining image sets first because they depend on images
+ iter = mResources.begin();
+ while (iter != mResources.end())
+ {
+#ifdef DEBUG_LEAKS
+ if (iter->second && iter->second->getRefCount())
+ {
+ ++iter;
+ continue;
+ }
+#endif
+ if (dynamic_cast<ImageSet*>(iter->second) != 0)
+ {
+ cleanUp(iter->second);
+ ResourceIterator toErase = iter;
+ ++iter;
+ mResources.erase(toErase);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+
+ // Release remaining resources, logging the number of dangling references.
+ iter = mResources.begin();
+ while (iter != mResources.end())
+ {
+#ifdef DEBUG_LEAKS
+ if (iter->second && iter->second->getRefCount())
+ {
+ ++iter;
+ continue;
+ }
+#endif
+ if (iter->second)
+ cleanUp(iter->second);
+ ++iter;
+ }
+}
+
+void ResourceManager::cleanUp(Resource *res)
+{
+ if (!res)
+ return;
+
+ if (res->mRefCount > 0)
+ {
+ logger->log("ResourceManager::~ResourceManager() cleaning up %d "
+ "reference%s to %s",
+ res->mRefCount,
+ (res->mRefCount == 1) ? "" : "s",
+ res->mIdPath.c_str());
+ }
+
+ delete res;
+}
+
+void ResourceManager::cleanOrphans()
+{
+ timeval tv;
+ gettimeofday(&tv, NULL);
+ // Delete orphaned resources after 30 seconds.
+ time_t oldest = tv.tv_sec, threshold = oldest - 30;
+
+ if (mOrphanedResources.empty() || mOldestOrphan >= threshold)
+ return;
+
+ ResourceIterator iter = mOrphanedResources.begin();
+ while (iter != mOrphanedResources.end())
+ {
+ Resource *res = iter->second;
+ if (!res)
+ {
+ ++iter;
+ continue;
+ }
+ time_t t = res->mTimeStamp;
+ if (t >= threshold)
+ {
+ if (t < oldest) oldest = t;
+ ++iter;
+ }
+ else
+ {
+ logger->log("ResourceManager::release(%s)", res->mIdPath.c_str());
+ ResourceIterator toErase = iter;
+ ++iter;
+ mOrphanedResources.erase(toErase);
+ delete res; // delete only after removal from list,
+ // to avoid issues in recursion
+ }
+ }
+
+ mOldestOrphan = oldest;
+}
+
+bool ResourceManager::setWriteDir(const std::string &path)
+{
+ return (bool) PHYSFS_setWriteDir(path.c_str());
+}
+
+bool ResourceManager::addToSearchPath(const std::string &path, bool append)
+{
+ logger->log("Adding to PhysicsFS: %s (%s)", path.c_str(),
+ append ? "append" : "prepend");
+ if (!PHYSFS_addToSearchPath(path.c_str(), append ? 1 : 0))
+ {
+ logger->log("Error: %s", PHYSFS_getLastError());
+ return false;
+ }
+ return true;
+}
+
+bool ResourceManager::removeFromSearchPath(const std::string &path)
+{
+ logger->log("Removing from PhysicsFS: %s", path.c_str());
+ if (!PHYSFS_removeFromSearchPath(path.c_str()))
+ {
+ logger->log("Error: %s", PHYSFS_getLastError());
+ return false;
+ }
+ return true;
+}
+
+void ResourceManager::searchAndAddArchives(const std::string &path,
+ const std::string &ext,
+ bool append)
+{
+ const char *dirSep = PHYSFS_getDirSeparator();
+ char **list = PHYSFS_enumerateFiles(path.c_str());
+
+ for (char **i = list; *i; i++)
+ {
+ size_t len = strlen(*i);
+
+ if (len > ext.length() && !ext.compare((*i) + (len - ext.length())))
+ {
+ std::string file, realPath, archive, realFixPath;
+
+ file = path + (*i);
+ realPath = std::string(PHYSFS_getRealDir(file.c_str()));
+ archive = realPath + dirSep + file;
+
+ addToSearchPath(archive, append);
+ }
+ }
+
+ PHYSFS_freeList(list);
+}
+
+void ResourceManager::searchAndRemoveArchives(const std::string &path,
+ const std::string &ext)
+{
+ const char *dirSep = PHYSFS_getDirSeparator();
+ char **list = PHYSFS_enumerateFiles(path.c_str());
+
+ for (char **i = list; *i; i++)
+ {
+ size_t len = strlen(*i);
+
+ if (len > ext.length() && !ext.compare((*i) + (len - ext.length())))
+ {
+ std::string file, realPath, archive, realFixPath;
+
+ file = path + (*i);
+ realPath = std::string(PHYSFS_getRealDir(file.c_str()));
+ archive = realPath + dirSep + file;
+
+ removeFromSearchPath(archive);
+ }
+ }
+
+ PHYSFS_freeList(list);
+}
+
+bool ResourceManager::mkdir(const std::string &path)
+{
+ return (bool) PHYSFS_mkdir(path.c_str());
+}
+
+bool ResourceManager::exists(const std::string &path)
+{
+ return PHYSFS_exists(path.c_str());
+}
+
+bool ResourceManager::isDirectory(const std::string &path)
+{
+ return PHYSFS_isDirectory(path.c_str());
+}
+
+std::string ResourceManager::getPath(const std::string &file)
+{
+ // get the real path to the file
+ const char* tmp = PHYSFS_getRealDir(file.c_str());
+ std::string path;
+
+ // if the file is not in the search path, then its NULL
+ if (tmp)
+ {
+ path = std::string(tmp) + "/" + file;
+ }
+ else
+ {
+ // if not found in search path return the default path
+ path = Client::getPackageDirectory() + "/" + file;
+ }
+
+ return path;
+}
+
+bool ResourceManager::addResource(const std::string &idPath,
+ Resource* resource)
+{
+ if (resource)
+ {
+ resource->incRef();
+ resource->mIdPath = idPath;
+ mResources[idPath] = resource;
+ return true;
+ }
+ return false;
+}
+
+Resource *ResourceManager::get(const std::string &idPath, generator fun,
+ void *data)
+{
+ // Check if the id exists, and return the value if it does.
+ ResourceIterator resIter = mResources.find(idPath);
+ if (resIter != mResources.end())
+ {
+ if (resIter->second)
+ resIter->second->incRef();
+ return resIter->second;
+ }
+
+ resIter = mOrphanedResources.find(idPath);
+ if (resIter != mOrphanedResources.end())
+ {
+ Resource *res = resIter->second;
+ mResources.insert(*resIter);
+ mOrphanedResources.erase(resIter);
+ if (res)
+ res->incRef();
+ return res;
+ }
+
+ Resource *resource = fun(data);
+
+ if (resource)
+ {
+ resource->incRef();
+ resource->mIdPath = idPath;
+ mResources[idPath] = resource;
+ cleanOrphans();
+ }
+ else
+ {
+ logger->log("Error loaging image: " + idPath);
+ }
+
+ // Returns NULL if the object could not be created.
+ return resource;
+}
+
+struct ResourceLoader
+{
+ ResourceManager *manager;
+ std::string path;
+ ResourceManager::loader fun;
+ static Resource *load(void *v)
+ {
+ ResourceLoader *l = static_cast< ResourceLoader * >(v);
+ int fileSize;
+ if (!l->manager)
+ return NULL;
+ void *buffer = l->manager->loadFile(l->path, fileSize);
+ if (!buffer)
+ return NULL;
+ Resource *res = l->fun(buffer, fileSize);
+ free(buffer);
+ return res;
+ }
+};
+
+Resource *ResourceManager::load(const std::string &path, loader fun)
+{
+ ResourceLoader l = { this, path, fun };
+ return get(path, ResourceLoader::load, &l);
+}
+
+Music *ResourceManager::getMusic(const std::string &idPath)
+{
+ return static_cast<Music*>(load(idPath, Music::load));
+}
+
+SoundEffect *ResourceManager::getSoundEffect(const std::string &idPath)
+{
+ return static_cast<SoundEffect*>(load(idPath, SoundEffect::load));
+}
+
+struct DyedImageLoader
+{
+ ResourceManager *manager;
+ std::string path;
+ static Resource *load(void *v)
+ {
+ if (!v)
+ return NULL;
+
+ DyedImageLoader *l = static_cast< DyedImageLoader * >(v);
+ if (!l->manager)
+ return NULL;
+
+ std::string path = l->path;
+ std::string::size_type p = path.find('|');
+ Dye *d = NULL;
+ if (p != std::string::npos)
+ {
+ d = new Dye(path.substr(p + 1));
+ path = path.substr(0, p);
+ }
+ int fileSize;
+ void *buffer = l->manager->loadFile(path, fileSize);
+ if (!buffer)
+ {
+ delete d;
+ return 0;
+ }
+ Resource *res = d ? Image::load(buffer, fileSize, *d)
+ : Image::load(buffer, fileSize);
+ free(buffer);
+ delete d;
+ return res;
+ }
+};
+
+Image *ResourceManager::getImage(const std::string &idPath)
+{
+ DyedImageLoader l = { this, idPath };
+ return static_cast<Image*>(get(idPath, DyedImageLoader::load, &l));
+}
+
+/*
+Image *ResourceManager::getSkinImage(const std::string &idPath)
+{
+ if (mSelectedSkin.empty())
+ return getImage(idPath);
+
+ DyedImageLoader l = { this, mSelectedSkin + idPath };
+ void *ptr = get(idPath, DyedImageLoader::load, &l);
+ if (ptr)
+ return static_cast<Image*>(ptr);
+ else
+ return getImage(idPath);
+}
+*/
+
+struct ImageSetLoader
+{
+ ResourceManager *manager;
+ std::string path;
+ int w, h;
+ static Resource *load(void *v)
+ {
+ if (!v)
+ return NULL;
+
+ ImageSetLoader *l = static_cast< ImageSetLoader * >(v);
+ if (!l->manager)
+ return NULL;
+
+ Image *img = l->manager->getImage(l->path);
+ if (!img)
+ return NULL;
+ ImageSet *res = new ImageSet(img, l->w, l->h);
+ img->decRef();
+ return res;
+ }
+};
+
+ImageSet *ResourceManager::getImageSet(const std::string &imagePath,
+ int w, int h)
+{
+ ImageSetLoader l = { this, imagePath, w, h };
+ std::stringstream ss;
+ ss << imagePath << "[" << w << "x" << h << "]";
+ return static_cast<ImageSet*>(get(ss.str(), ImageSetLoader::load, &l));
+}
+
+struct SpriteDefLoader
+{
+ std::string path;
+ int variant;
+ static Resource *load(void *v)
+ {
+ if (!v)
+ return NULL;
+
+ SpriteDefLoader *l = static_cast< SpriteDefLoader * >(v);
+ return SpriteDef::load(l->path, l->variant);
+ }
+};
+
+SpriteDef *ResourceManager::getSprite(const std::string &path, int variant)
+{
+ SpriteDefLoader l = { path, variant };
+ std::stringstream ss;
+ ss << path << "[" << variant << "]";
+ return static_cast<SpriteDef*>(get(ss.str(), SpriteDefLoader::load, &l));
+}
+
+void ResourceManager::release(Resource *res)
+{
+ if (!res)
+ return;
+
+ ResourceIterator resIter = mResources.find(res->mIdPath);
+
+ // The resource has to exist
+ assert(resIter != mResources.end() && resIter->second == res);
+
+ timeval tv;
+ gettimeofday(&tv, NULL);
+ time_t timestamp = tv.tv_sec;
+
+ res->mTimeStamp = timestamp;
+ if (mOrphanedResources.empty()) mOldestOrphan = timestamp;
+
+ mOrphanedResources.insert(*resIter);
+ mResources.erase(resIter);
+}
+
+ResourceManager *ResourceManager::getInstance()
+{
+ // Create a new instance if necessary.
+ if (!instance)
+ instance = new ResourceManager;
+ return instance;
+}
+
+void ResourceManager::deleteInstance()
+{
+ delete instance;
+ instance = 0;
+}
+
+void *ResourceManager::loadFile(const std::string &fileName, int &fileSize)
+{
+ // Attempt to open the specified file using PhysicsFS
+ PHYSFS_file *file = PHYSFS_openRead(fileName.c_str());
+
+ // If the handler is an invalid pointer indicate failure
+ if (file == NULL)
+ {
+ logger->log("Warning: Failed to load %s: %s",
+ fileName.c_str(), PHYSFS_getLastError());
+ return NULL;
+ }
+
+ // Log the real dir of the file
+ logger->log("Loaded %s/%s", PHYSFS_getRealDir(fileName.c_str()),
+ fileName.c_str());
+
+ // Get the size of the file
+ fileSize = static_cast<int>(PHYSFS_fileLength(file));
+
+ // Allocate memory and load the file
+ void *buffer = malloc(fileSize);
+ PHYSFS_read(file, buffer, 1, fileSize);
+
+ // Close the file and let the user deallocate the memory
+ PHYSFS_close(file);
+
+ return buffer;
+}
+
+bool ResourceManager::copyFile(const std::string &src, const std::string &dst)
+{
+ PHYSFS_file *srcFile = PHYSFS_openRead(src.c_str());
+ if (!srcFile)
+ {
+ logger->log("Read error: %s", PHYSFS_getLastError());
+ return false;
+ }
+ PHYSFS_file *dstFile = PHYSFS_openWrite(dst.c_str());
+ if (!dstFile)
+ {
+ logger->log("Write error: %s", PHYSFS_getLastError());
+ PHYSFS_close(srcFile);
+ return false;
+ }
+
+ int fileSize = static_cast<int>(PHYSFS_fileLength(srcFile));
+ void *buf = malloc(fileSize);
+ PHYSFS_read(srcFile, buf, 1, fileSize);
+ PHYSFS_write(dstFile, buf, 1, fileSize);
+
+ PHYSFS_close(srcFile);
+ PHYSFS_close(dstFile);
+ free(buf);
+ return true;
+}
+
+std::vector<std::string> ResourceManager::loadTextFile(
+ const std::string &fileName)
+{
+ int contentsLength;
+ char *fileContents = (char*)loadFile(fileName, contentsLength);
+ std::vector<std::string> lines;
+
+ if (!fileContents)
+ {
+ logger->log("Couldn't load text file: %s", fileName.c_str());
+ return lines;
+ }
+
+ std::istringstream iss(std::string(fileContents, contentsLength));
+ std::string line;
+
+ while (getline(iss, line))
+ lines.push_back(line);
+
+ free(fileContents);
+ return lines;
+}
+
+SDL_Surface *ResourceManager::loadSDLSurface(const std::string &filename)
+{
+ int fileSize;
+ void *buffer = loadFile(filename, fileSize);
+ SDL_Surface *tmp = NULL;
+
+ if (buffer)
+ {
+ SDL_RWops *rw = SDL_RWFromMem(buffer, fileSize);
+ tmp = IMG_Load_RW(rw, 1);
+ ::free(buffer);
+ }
+
+ return tmp;
+}
+
+void ResourceManager::scheduleDelete(SDL_Surface* surface)
+{
+ deletedSurfaces.insert(surface);
+}
+
+void ResourceManager::clearScheduled()
+{
+ for (std::set<SDL_Surface*>::iterator i = deletedSurfaces.begin(),
+ i_end = deletedSurfaces.end(); i != i_end; ++i)
+ {
+ SDL_FreeSurface(*i);
+ }
+ deletedSurfaces.clear();
+}
diff --git a/src/resources/resourcemanager.h b/src/resources/resourcemanager.h
new file mode 100644
index 000000000..163369a64
--- /dev/null
+++ b/src/resources/resourcemanager.h
@@ -0,0 +1,265 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RESOURCE_MANAGER_H
+#define RESOURCE_MANAGER_H
+
+#include <ctime>
+#include <map>
+#include <string>
+#include <vector>
+#include <set>
+
+class Image;
+class ImageSet;
+class Music;
+class Resource;
+class SoundEffect;
+class SpriteDef;
+struct SDL_Surface;
+
+/**
+ * A class for loading and managing resources.
+ */
+class ResourceManager
+{
+ friend class Resource;
+
+ public:
+
+ typedef Resource *(*loader)(void *, unsigned);
+ typedef Resource *(*generator)(void *);
+
+ ResourceManager();
+
+ /**
+ * Destructor. Cleans up remaining resources, warning about resources
+ * that were still referenced.
+ */
+ ~ResourceManager();
+
+ /**
+ * Sets the write directory.
+ *
+ * @param path The path of the directory to be added.
+ * @return <code>true</code> on success, <code>false</code> otherwise.
+ */
+ bool setWriteDir(const std::string &path);
+
+ /**
+ * Adds a directory or archive to the search path. If append is true
+ * then the directory is added to the end of the search path, otherwise
+ * it is added at the front.
+ *
+ * @return <code>true</code> on success, <code>false</code> otherwise.
+ */
+ bool addToSearchPath(const std::string &path, bool append);
+
+ /**
+ * Remove a directory or archive from the search path.
+ *
+ * @return <code>true</code> on success, <code>false</code> otherwise.
+ */
+ bool removeFromSearchPath(const std::string &path);
+
+ /**
+ * Searches for zip files and adds them to the search path.
+ */
+ void searchAndAddArchives(const std::string &path,
+ const std::string &ext,
+ bool append);
+
+ /**
+ * Searches for zip files and remove them from the search path.
+ */
+ void searchAndRemoveArchives(const std::string &path,
+ const std::string &ext);
+
+ /**
+ * Creates a directory in the write path
+ */
+ bool mkdir(const std::string &path);
+
+ /**
+ * Checks whether the given file or directory exists in the search path
+ */
+ bool exists(const std::string &path);
+
+ /**
+ * Checks whether the given path is a directory.
+ */
+ bool isDirectory(const std::string &path);
+
+ /**
+ * Returns the real path to a file. Note that this method will always
+ * return a path, it does not check whether the file exists.
+ *
+ * @param file The file to get the real path to.
+ * @return The real path.
+ */
+ std::string getPath(const std::string &file);
+
+ /**
+ * Creates a resource and adds it to the resource map.
+ *
+ * @param idPath The resource identifier path.
+ * @param fun A function for generating the resource.
+ * @param data Extra parameters for the generator.
+ * @return A valid resource or <code>NULL</code> if the resource could
+ * not be generated.
+ */
+ Resource *get(const std::string &idPath, generator fun, void *data);
+
+ /**
+ * Loads a resource from a file and adds it to the resource map.
+ *
+ * @param path The file name.
+ * @param fun A function for parsing the file.
+ * @return A valid resource or <code>NULL</code> if the resource could
+ * not be loaded.
+ */
+ Resource *load(const std::string &path, loader fun);
+
+ /**
+ * Adds a preformatted resource to the resource map.
+ *
+ * @param path The file name.
+ * @param Resource The Resource to add.
+ * @return true if successfull, false otherwise.
+ */
+ bool addResource(const std::string &idPath, Resource* resource);
+
+ /**
+ * Copies a file from one place to another (useful for extracting
+ * raw files from a zip archive, for example)
+ *
+ * @param src Source file name
+ * @param dst Destination file name
+ * @return true on success, false on failure. An error message should be
+ * in the log file.
+ */
+ bool copyFile(const std::string &src, const std::string &dst);
+
+ /**
+ * Convenience wrapper around ResourceManager::get for loading
+ * images.
+ */
+ Image *getImage(const std::string &idPath);
+
+ /**
+ * Convenience wrapper around ResourceManager::get for loading
+ * songs.
+ */
+ Music *getMusic(const std::string &idPath);
+
+ /**
+ * Convenience wrapper around ResourceManager::get for loading
+ * samples.
+ */
+ SoundEffect *getSoundEffect(const std::string &idPath);
+
+ /**
+ * Creates a image set based on the image referenced by the given
+ * path and the supplied sprite sizes
+ */
+ ImageSet *getImageSet(const std::string &imagePath, int w, int h);
+
+ /**
+ * Creates a sprite definition based on a given path and the supplied
+ * variant.
+ */
+ SpriteDef *getSprite(const std::string &path, int variant = 0);
+
+ /**
+ * Releases a resource, placing it in the set of orphaned resources.
+ */
+ void release(Resource *);
+
+ /**
+ * Allocates data into a buffer pointer for raw data loading. The
+ * returned data is expected to be freed using <code>free()</code>.
+ *
+ * @param fileName The name of the file to be loaded.
+ * @param fileSize The size of the file that was loaded.
+ *
+ * @return An allocated byte array containing the data that was loaded,
+ * or <code>NULL</code> on fail.
+ */
+ void *loadFile(const std::string &fileName, int &fileSize);
+
+ /**
+ * Retrieves the contents of a text file.
+ */
+ std::vector<std::string> loadTextFile(const std::string &fileName);
+
+ /**
+ * Loads the given filename as an SDL surface. The returned surface is
+ * expected to be freed by the caller using SDL_FreeSurface.
+ */
+ SDL_Surface *loadSDLSurface(const std::string &filename);
+
+ void scheduleDelete(SDL_Surface* surface);
+
+ void clearScheduled();
+
+ /**
+ * Returns an instance of the class, creating one if it does not
+ * already exist.
+ */
+ static ResourceManager *getInstance();
+
+ /**
+ * Deletes the class instance if it exists.
+ */
+ static void deleteInstance();
+
+/*
+ void selectSkin();
+
+ Image *getSkinImage(const std::string &idPath);
+
+ std::string mapPathToSkin(const std::string &file);
+
+ void fillSkinsList(std::vector<std::string> &list) const;
+
+ std::string getSkinName() const { return mSkinName; }
+*/
+
+ private:
+ /**
+ * Deletes the resource after logging a cleanup message.
+ */
+ static void cleanUp(Resource *resource);
+
+ void cleanOrphans();
+
+ static ResourceManager *instance;
+ typedef std::map<std::string, Resource*> Resources;
+ typedef Resources::iterator ResourceIterator;
+ std::set<SDL_Surface*> deletedSurfaces;
+ Resources mResources;
+ Resources mOrphanedResources;
+ time_t mOldestOrphan;
+ std::string mSelectedSkin;
+ std::string mSkinName;
+};
+
+#endif
diff --git a/src/resources/soundeffect.cpp b/src/resources/soundeffect.cpp
new file mode 100644
index 000000000..823529c63
--- /dev/null
+++ b/src/resources/soundeffect.cpp
@@ -0,0 +1,58 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/soundeffect.h"
+
+#include "log.h"
+
+SoundEffect::~SoundEffect()
+{
+ Mix_FreeChunk(mChunk);
+}
+
+Resource *SoundEffect::load(void *buffer, unsigned bufferSize)
+{
+ if (!buffer)
+ return NULL;
+
+ // Load the raw file data from the buffer in an RWops structure
+ SDL_RWops *rw = SDL_RWFromMem(buffer, bufferSize);
+
+ // Load the music data and free the RWops structure
+ Mix_Chunk *tmpSoundEffect = Mix_LoadWAV_RW(rw, 1);
+
+ if (tmpSoundEffect)
+ {
+ return new SoundEffect(tmpSoundEffect);
+ }
+ else
+ {
+ logger->log("Error, failed to load sound effect: %s", Mix_GetError());
+ return NULL;
+ }
+}
+
+bool SoundEffect::play(int loops, int volume)
+{
+ Mix_VolumeChunk(mChunk, volume);
+
+ return Mix_PlayChannel(-1, mChunk, loops) != -1;
+}
diff --git a/src/resources/soundeffect.h b/src/resources/soundeffect.h
new file mode 100644
index 000000000..e7c832f42
--- /dev/null
+++ b/src/resources/soundeffect.h
@@ -0,0 +1,75 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SOUND_EFFECT_H
+#define SOUND_EFFECT_H
+
+#include "resources/resource.h"
+
+#ifdef __APPLE__
+#include <SDL_mixer/SDL_mixer.h>
+#else
+#include <SDL_mixer.h>
+#endif
+
+/**
+ * Defines a class for loading and storing sound effects.
+ */
+class SoundEffect : public Resource
+{
+ public:
+ /**
+ * Destructor.
+ */
+ virtual ~SoundEffect();
+
+ /**
+ * Loads a sample from a buffer in memory.
+ *
+ * @param buffer The memory buffer containing the sample data.
+ * @param bufferSize The size of the memory buffer in bytes.
+ *
+ * @return <code>NULL</code> if the an error occurred, a valid pointer
+ * otherwise.
+ */
+ static Resource *load(void *buffer, unsigned bufferSize);
+
+ /**
+ * Plays the sample.
+ *
+ * @param loops Number of times to repeat the playback.
+ * @param volume Sample playback volume.
+ *
+ * @return <code>true</code> if the playback started properly
+ * <code>false</code> otherwise.
+ */
+ virtual bool play(int loops, int volume);
+
+ protected:
+ /**
+ * Constructor.
+ */
+ SoundEffect(Mix_Chunk *soundEffect): mChunk(soundEffect) {}
+
+ Mix_Chunk *mChunk;
+};
+
+#endif // SOUND_EFFECT_H
diff --git a/src/resources/specialdb.cpp b/src/resources/specialdb.cpp
new file mode 100644
index 000000000..93edfb683
--- /dev/null
+++ b/src/resources/specialdb.cpp
@@ -0,0 +1,132 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/specialdb.h"
+
+#include "log.h"
+
+#include "utils/dtor.h"
+#include "utils/xml.h"
+
+
+namespace
+{
+ SpecialInfos mSpecialInfos;
+ bool mLoaded = false;
+}
+
+SpecialInfo::TargetMode SpecialDB::targetModeFromString(const std::string& str)
+{
+ if (str == "self") return SpecialInfo::TARGET_SELF;
+ else if (str == "friend") return SpecialInfo::TARGET_FRIEND;
+ else if (str == "enemy") return SpecialInfo::TARGET_ENEMY;
+ else if (str == "being") return SpecialInfo::TARGET_BEING;
+ else if (str == "point") return SpecialInfo::TARGET_POINT;
+
+ logger->log("SpecialDB: Warning, unknown target mode \"%s\"",
+ str.c_str() );
+ return SpecialInfo::TARGET_SELF;
+}
+
+void SpecialDB::load()
+{
+ if (mLoaded)
+ unload();
+
+ logger->log("Initializing special database...");
+
+ XML::Document doc("specials.xml");
+ xmlNodePtr root = doc.rootNode();
+
+ if (!root || !xmlStrEqual(root->name, BAD_CAST "specials"))
+ {
+ logger->log("Error loading specials file specials.xml");
+ return;
+ }
+
+ std::string setName;
+
+ for_each_xml_child_node(set, root)
+ {
+ if (xmlStrEqual(set->name, BAD_CAST "set"))
+ {
+ setName = XML::getProperty(set, "name", "Actions");
+
+ for_each_xml_child_node(special, set)
+ {
+ if (xmlStrEqual(special->name, BAD_CAST "special"))
+ {
+ SpecialInfo *info = new SpecialInfo();
+ int id = XML::getProperty(special, "id", 0);
+ info->id = id;
+ info->set = setName;
+ info->name = XML::getProperty(special, "name", "");
+ info->icon = XML::getProperty(special, "icon", "");
+
+ info->isActive = XML::getBoolProperty(
+ special, "active", false);
+ info->targetMode = targetModeFromString(XML::getProperty(
+ special, "target", "self"));
+
+ info->level = XML::getProperty(special, "level", -1);
+ info->hasLevel = info->level > -1;
+
+ info->hasRechargeBar = XML::getBoolProperty(special,
+ "recharge", false);
+ info->rechargeNeeded = 0;
+ info->rechargeCurrent = 0;
+
+ if (mSpecialInfos.find(id) != mSpecialInfos.end())
+ {
+ logger->log("SpecialDB: Duplicate special ID"
+ " %d (ignoring)", id);
+ }
+ else
+ {
+ mSpecialInfos[id] = info;
+ }
+ }
+ }
+ }
+ }
+
+ mLoaded = true;
+}
+
+void SpecialDB::unload()
+{
+ delete_all(mSpecialInfos);
+ mSpecialInfos.clear();
+
+ mLoaded = false;
+}
+
+
+SpecialInfo *SpecialDB::get(int id)
+{
+ SpecialInfos::iterator i = mSpecialInfos.find(id);
+
+ if (i == mSpecialInfos.end())
+ return NULL;
+ else
+ return i->second;
+ return NULL;
+}
+
diff --git a/src/resources/specialdb.h b/src/resources/specialdb.h
new file mode 100644
index 000000000..988651723
--- /dev/null
+++ b/src/resources/specialdb.h
@@ -0,0 +1,72 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SPECIAL_DB_H
+#define SPECIAL_DB_H
+
+#include <string>
+#include <map>
+
+struct SpecialInfo
+{
+ enum TargetMode
+ {
+ TARGET_SELF = 0, // no target selection
+ TARGET_FRIEND, // target friendly being
+ TARGET_ENEMY, // target hostile being
+ TARGET_BEING, // target any being
+ TARGET_POINT // target map location
+ };
+ int id;
+ std::string set; // tab on which the special is shown
+ std::string name; // displayed name of special
+ std::string icon; // filename of graphical icon
+
+ bool isActive; // true when the special can be used
+ TargetMode targetMode; // target mode
+
+ bool hasLevel; // true when the special has levels
+ int level; // level of special when applicable
+
+ bool hasRechargeBar; // true when the special has a recharge bar
+ int rechargeNeeded; // maximum recharge when applicable
+ int rechargeCurrent; // current recharge when applicable
+};
+
+/**
+ * Special information database.
+ */
+namespace SpecialDB
+{
+ void load();
+
+ void unload();
+
+ /** gets the special info for ID. Will return 0 when it is
+ * a server-specific special.
+ */
+ SpecialInfo *get(int id);
+
+ SpecialInfo::TargetMode targetModeFromString(const std::string& str);
+}
+
+typedef std::map<int, SpecialInfo *> SpecialInfos;
+
+#endif
diff --git a/src/resources/spritedef.cpp b/src/resources/spritedef.cpp
new file mode 100644
index 000000000..dddee575f
--- /dev/null
+++ b/src/resources/spritedef.cpp
@@ -0,0 +1,339 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/spritedef.h"
+
+#include "log.h"
+
+#include "resources/action.h"
+#include "resources/animation.h"
+#include "resources/dye.h"
+#include "resources/image.h"
+#include "resources/imageset.h"
+#include "resources/resourcemanager.h"
+
+#include "configuration.h"
+
+#include "utils/xml.h"
+
+#include <set>
+
+SpriteReference *SpriteReference::Empty = new SpriteReference(
+ paths.getStringValue("spriteErrorFile"), 0);
+
+Action *SpriteDef::getAction(std::string action) const
+{
+ Actions::const_iterator i = mActions.find(action);
+
+ if (i == mActions.end())
+ {
+ logger->log("Warning: no action \"%s\" defined!", action.c_str());
+ return NULL;
+ }
+
+ return i->second;
+}
+
+SpriteDef *SpriteDef::load(const std::string &animationFile, int variant)
+{
+ std::string::size_type pos = animationFile.find('|');
+ std::string palettes;
+ if (pos != std::string::npos)
+ palettes = animationFile.substr(pos + 1);
+
+ XML::Document doc(animationFile.substr(0, pos));
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "sprite"))
+ {
+ logger->log("Error, failed to parse %s", animationFile.c_str());
+
+ std::string errorFile = paths.getStringValue("sprites")
+ + paths.getStringValue("spriteErrorFile");
+ if (animationFile != errorFile)
+ return load(errorFile, 0);
+ else
+ return NULL;
+ }
+
+ SpriteDef *def = new SpriteDef;
+ def->loadSprite(rootNode, variant, palettes);
+ def->substituteActions();
+ return def;
+}
+
+void SpriteDef::substituteAction(std::string complete, std::string with)
+{
+ if (mActions.find(complete) == mActions.end())
+ {
+ Actions::iterator i = mActions.find(with);
+ if (i != mActions.end())
+ mActions[complete] = i->second;
+ }
+}
+
+void SpriteDef::substituteActions()
+{
+ substituteAction(SpriteAction::STAND, SpriteAction::DEFAULT);
+ substituteAction(SpriteAction::MOVE, SpriteAction::STAND);
+ substituteAction(SpriteAction::ATTACK, SpriteAction::STAND);
+ substituteAction(SpriteAction::CAST_MAGIC, SpriteAction::ATTACK);
+ substituteAction(SpriteAction::USE_ITEM, SpriteAction::CAST_MAGIC);
+ substituteAction(SpriteAction::SIT, SpriteAction::STAND);
+ substituteAction(SpriteAction::SLEEP, SpriteAction::SIT);
+ substituteAction(SpriteAction::HURT, SpriteAction::STAND);
+ substituteAction(SpriteAction::DEAD, SpriteAction::HURT);
+}
+
+void SpriteDef::loadSprite(xmlNodePtr spriteNode, int variant,
+ const std::string &palettes)
+{
+ // Get the variant
+ const int variantCount = XML::getProperty(spriteNode, "variants", 0);
+ int variant_offset = 0;
+
+ if (variantCount > 0 && variant < variantCount)
+ {
+ variant_offset =
+ variant * XML::getProperty(spriteNode, "variant_offset", 0);
+ }
+
+ for_each_xml_child_node(node, spriteNode)
+ {
+ if (xmlStrEqual(node->name, BAD_CAST "imageset"))
+ loadImageSet(node, palettes);
+ else if (xmlStrEqual(node->name, BAD_CAST "action"))
+ loadAction(node, variant_offset);
+ else if (xmlStrEqual(node->name, BAD_CAST "include"))
+ includeSprite(node);
+ }
+}
+
+void SpriteDef::loadImageSet(xmlNodePtr node, const std::string &palettes)
+{
+ const std::string name = XML::getProperty(node, "name", "");
+
+ // We don't allow redefining image sets. This way, an included sprite
+ // definition will use the already loaded image set with the same name.
+ if (mImageSets.find(name) != mImageSets.end())
+ return;
+
+ const int width = XML::getProperty(node, "width", 0);
+ const int height = XML::getProperty(node, "height", 0);
+ std::string imageSrc = XML::getProperty(node, "src", "");
+ Dye::instantiate(imageSrc, palettes);
+
+ ResourceManager *resman = ResourceManager::getInstance();
+ ImageSet *imageSet = resman->getImageSet(imageSrc, width, height);
+
+ if (!imageSet)
+ {
+ logger->log1("Couldn't load imageset!");
+ return;
+ }
+
+ mImageSets[name] = imageSet;
+}
+
+void SpriteDef::loadAction(xmlNodePtr node, int variant_offset)
+{
+ const std::string actionName = XML::getProperty(node, "name", "");
+ const std::string imageSetName = XML::getProperty(node, "imageset", "");
+
+ ImageSetIterator si = mImageSets.find(imageSetName);
+ if (si == mImageSets.end())
+ {
+ logger->log("Warning: imageset \"%s\" not defined in %s",
+ imageSetName.c_str(), getIdPath().c_str());
+ return;
+ }
+ ImageSet *imageSet = si->second;
+
+ if (actionName == SpriteAction::INVALID)
+ {
+ logger->log("Warning: Unknown action \"%s\" defined in %s",
+ actionName.c_str(), getIdPath().c_str());
+ return;
+ }
+ Action *action = new Action;
+ mActions[actionName] = action;
+
+ // dirty hack to fix bad resources in tmw server
+ if (actionName == "attack_stab")
+ mActions["attack"] = action;
+
+ // When first action set it as default direction
+ if (mActions.size() == 1)
+ mActions[SpriteAction::DEFAULT] = action;
+
+ // Load animations
+ for_each_xml_child_node(animationNode, node)
+ {
+ if (xmlStrEqual(animationNode->name, BAD_CAST "animation"))
+ loadAnimation(animationNode, action, imageSet, variant_offset);
+ }
+}
+
+void SpriteDef::loadAnimation(xmlNodePtr animationNode,
+ Action *action, ImageSet *imageSet,
+ int variant_offset)
+{
+ if (!action || !imageSet)
+ return;
+
+ const std::string directionName =
+ XML::getProperty(animationNode, "direction", "");
+ const SpriteDirection directionType = makeSpriteDirection(directionName);
+
+ if (directionType == DIRECTION_INVALID)
+ {
+ logger->log("Warning: Unknown direction \"%s\" used in %s",
+ directionName.c_str(), getIdPath().c_str());
+ return;
+ }
+
+ Animation *animation = new Animation;
+ action->setAnimation(directionType, animation);
+
+ // Get animation frames
+ for_each_xml_child_node(frameNode, animationNode)
+ {
+ const int delay = XML::getProperty(frameNode, "delay", 0);
+ int offsetX = XML::getProperty(frameNode, "offsetX", 0);
+ int offsetY = XML::getProperty(frameNode, "offsetY", 0);
+ offsetY -= imageSet->getHeight() - 32;
+ offsetX -= imageSet->getWidth() / 2 - 16;
+
+ if (xmlStrEqual(frameNode->name, BAD_CAST "frame"))
+ {
+ const int index = XML::getProperty(frameNode, "index", -1);
+
+ if (index < 0)
+ {
+ logger->log1("No valid value for 'index'");
+ continue;
+ }
+
+ Image *img = imageSet->get(index + variant_offset);
+
+ if (!img)
+ {
+ logger->log("No image at index %d", index + variant_offset);
+ continue;
+ }
+
+ animation->addFrame(img, delay, offsetX, offsetY);
+ }
+ else if (xmlStrEqual(frameNode->name, BAD_CAST "sequence"))
+ {
+ int start = XML::getProperty(frameNode, "start", -1);
+ const int end = XML::getProperty(frameNode, "end", -1);
+
+ if (start < 0 || end < 0)
+ {
+ logger->log1("No valid value for 'start' or 'end'");
+ continue;
+ }
+
+ while (end >= start)
+ {
+ Image *img = imageSet->get(start + variant_offset);
+
+ if (!img)
+ {
+ logger->log("No image at index %d",
+ start + variant_offset);
+ start++;
+ continue;
+ }
+
+ animation->addFrame(img, delay, offsetX, offsetY);
+ start++;
+ }
+ }
+ else if (xmlStrEqual(frameNode->name, BAD_CAST "end"))
+ {
+ animation->addTerminator();
+ }
+ } // for frameNode
+}
+
+void SpriteDef::includeSprite(xmlNodePtr includeNode)
+{
+ // TODO: Perform circular dependency check, since it's easy to crash the
+ // client this way.
+ const std::string filename = XML::getProperty(includeNode, "file", "");
+
+ if (filename.empty())
+ return;
+
+ XML::Document doc(paths.getStringValue("sprites") + filename);
+ xmlNodePtr rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "sprite"))
+ {
+ logger->log("Error, no sprite root node in %s", filename.c_str());
+ return;
+ }
+
+ loadSprite(rootNode, 0);
+}
+
+SpriteDef::~SpriteDef()
+{
+ // Actions are shared, so ensure they are deleted only once.
+ std::set< Action * > actions;
+ for (Actions::const_iterator i = mActions.begin(),
+ i_end = mActions.end(); i != i_end; ++i)
+ {
+ actions.insert(i->second);
+ }
+
+ for (std::set< Action * >::const_iterator i = actions.begin(),
+ i_end = actions.end(); i != i_end; ++i)
+ {
+ delete *i;
+ }
+
+//need actions.clear?
+
+ for (ImageSetIterator i = mImageSets.begin();
+ i != mImageSets.end(); ++i)
+ {
+ i->second->decRef();
+ }
+}
+
+SpriteDirection SpriteDef::makeSpriteDirection(const std::string &direction)
+{
+ if (direction.empty() || direction == "default")
+ return DIRECTION_DEFAULT;
+ else if (direction == "up")
+ return DIRECTION_UP;
+ else if (direction == "left")
+ return DIRECTION_LEFT;
+ else if (direction == "right")
+ return DIRECTION_RIGHT;
+ else if (direction == "down")
+ return DIRECTION_DOWN;
+ else
+ return DIRECTION_INVALID;
+}
diff --git a/src/resources/spritedef.h b/src/resources/spritedef.h
new file mode 100644
index 000000000..84582c779
--- /dev/null
+++ b/src/resources/spritedef.h
@@ -0,0 +1,176 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SPRITEDEF_H
+#define SPRITEDEF_H
+
+#include "resources/resource.h"
+
+#include <libxml/tree.h>
+
+#include <list>
+#include <map>
+#include <string>
+
+class Action;
+class ImageSet;
+
+struct SpriteReference
+{
+ static SpriteReference *Empty;
+
+ SpriteReference():
+ sprite(""), variant(0)
+ {}
+
+ SpriteReference(std::string sprite, int variant)
+ { this->sprite = sprite; this->variant = variant; }
+
+ std::string sprite;
+ int variant;
+};
+
+struct SpriteDisplay
+{
+ std::string image;
+ std::list<SpriteReference*> sprites;
+ std::list<std::string> particles;
+};
+
+typedef std::list<SpriteReference*>::const_iterator SpriteRefs;
+
+/*
+ * Remember those are the main action.
+ * Action subtypes, e.g.: "attack_bow" are to be passed by items.xml after
+ * an ACTION_ATTACK call.
+ * Which special to be use to to be passed with the USE_SPECIAL call.
+ * Running, walking, ... is a sub-type of moving.
+ * ...
+ * Please don't add hard-coded subtypes here!
+ */
+namespace SpriteAction
+{
+ static const std::string DEFAULT = "stand";
+ static const std::string STAND = "stand";
+ static const std::string SIT = "sit";
+ static const std::string SLEEP = "sleep";
+ static const std::string DEAD = "dead";
+ static const std::string MOVE = "walk";
+ static const std::string ATTACK = "attack";
+ static const std::string HURT = "hurt";
+ static const std::string USE_SPECIAL = "special";
+ static const std::string CAST_MAGIC = "magic";
+ static const std::string USE_ITEM = "item";
+ static const std::string INVALID = "";
+}
+
+enum SpriteDirection
+{
+ DIRECTION_DEFAULT = 0,
+ DIRECTION_UP,
+ DIRECTION_DOWN,
+ DIRECTION_LEFT,
+ DIRECTION_RIGHT,
+ DIRECTION_INVALID
+};
+
+/**
+ * Defines a class to load an animation.
+ */
+class SpriteDef : public Resource
+{
+ public:
+ /**
+ * Loads a sprite definition file.
+ */
+ static SpriteDef *load(const std::string &file, int variant);
+
+ /**
+ * Returns the specified action.
+ */
+ Action *getAction(std::string action) const;
+
+ /**
+ * Converts a string into a SpriteDirection enum.
+ */
+ static SpriteDirection
+ makeSpriteDirection(const std::string &direction);
+
+ private:
+ /**
+ * Constructor.
+ */
+ SpriteDef() {}
+
+ /**
+ * Destructor.
+ */
+ ~SpriteDef();
+
+ /**
+ * Loads a sprite element.
+ */
+ void loadSprite(xmlNodePtr spriteNode, int variant,
+ const std::string &palettes = "");
+
+ /**
+ * Loads an imageset element.
+ */
+ void loadImageSet(xmlNodePtr node, const std::string &palettes);
+
+ /**
+ * Loads an action element.
+ */
+ void loadAction(xmlNodePtr node, int variant_offset);
+
+ /**
+ * Loads an animation element.
+ */
+ void loadAnimation(xmlNodePtr animationNode,
+ Action *action, ImageSet *imageSet,
+ int variant_offset);
+
+ /**
+ * Include another sprite into this one.
+ */
+ void includeSprite(xmlNodePtr includeNode);
+
+ /**
+ * Complete missing actions by copying existing ones.
+ */
+ void substituteActions();
+
+ /**
+ * When there are no animations defined for the action "complete", its
+ * animations become a copy of those of the action "with".
+ */
+ void substituteAction(std::string complete, std::string with);
+
+ typedef std::map<std::string, ImageSet*> ImageSets;
+ typedef ImageSets::iterator ImageSetIterator;
+
+ typedef std::map<std::string, Action*> Actions;
+
+ ImageSets mImageSets;
+ Actions mActions;
+};
+
+#endif // SPRITEDEF_H
diff --git a/src/resources/wallpaper.cpp b/src/resources/wallpaper.cpp
new file mode 100644
index 000000000..d3ccef38b
--- /dev/null
+++ b/src/resources/wallpaper.cpp
@@ -0,0 +1,172 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/wallpaper.h"
+
+#include "resources/resourcemanager.h"
+#include "log.h"
+
+#include "utils/stringutils.h"
+#include "configuration.h"
+
+#include <physfs.h>
+
+#include <algorithm>
+#include <cstring>
+#include <time.h>
+#include <vector>
+
+//define WALLPAPER_FOLDER "graphics/images/"
+//define WALLPAPER_BASE "login_wallpaper.png"
+
+struct WallpaperData
+{
+ std::string filename;
+ int width;
+ int height;
+};
+
+bool wallpaperCompare(WallpaperData a, WallpaperData b);
+
+static std::vector<WallpaperData> wallpaperData;
+static bool haveBackup; // Is the backup (no size given) version available?
+
+static std::string wallpaperPath;
+static std::string wallpaperFile;
+
+// Search for the wallpaper path values sequentially..
+static void initDefaultWallpaperPaths()
+{
+ ResourceManager *resman = ResourceManager::getInstance();
+
+ // Init the path
+ wallpaperPath = branding.getStringValue("wallpapersPath");
+
+ if (!wallpaperPath.empty() && resman->isDirectory(wallpaperPath))
+ return;
+ else
+ wallpaperPath = paths.getValue("wallpapers", "graphics/images/");
+
+ // Init the default file
+ wallpaperFile = branding.getStringValue("wallpaperFile");
+
+ if (!wallpaperFile.empty())
+ return;
+ else
+ wallpaperFile = paths.getValue("wallpaperFile", "login_wallpaper.png");
+}
+
+bool wallpaperCompare(WallpaperData a, WallpaperData b)
+{
+ int aa = a.width * a.height;
+ int ab = b.width * b.height;
+
+ return (aa > ab || (aa == ab && a.width > b.width));
+}
+#include <iostream>
+void Wallpaper::loadWallpapers()
+{
+ wallpaperData.clear();
+
+ initDefaultWallpaperPaths();
+
+ char **imgs = PHYSFS_enumerateFiles(wallpaperPath.c_str());
+
+ for (char **i = imgs; *i != NULL; i++)
+ {
+ int width;
+ int height;
+
+ // If the backup file is found, we tell it.
+ if (strncmp (*i, wallpaperFile.c_str(), strlen(*i)) == 0)
+ haveBackup = true;
+
+ // If the image format is terminated by: "_<width>x<height>.png"
+ // It is taken as a potential wallpaper.
+
+ // First, get the base filename of the image:
+ std::string filename = *i;
+ unsigned long separator = filename.rfind("_");
+ filename = filename.substr(0, separator);
+
+ // Check that the base filename doesn't have any '%' markers.
+ separator = filename.find("%");
+ if (separator == std::string::npos)
+ {
+ // Then, append the width and height search mask.
+ filename.append("_%dx%d.png");
+
+ if (sscanf(*i, filename.c_str(), &width, &height) == 2)
+ {
+ WallpaperData wp;
+ wp.filename = wallpaperPath;
+ wp.filename.append(*i);
+ wp.width = width;
+ wp.height = height;
+ wallpaperData.push_back(wp);
+ }
+ }
+ }
+
+ PHYSFS_freeList(imgs);
+
+ std::sort(wallpaperData.begin(), wallpaperData.end(), wallpaperCompare);
+}
+
+std::string Wallpaper::getWallpaper(int width, int height)
+{
+ std::vector<WallpaperData>::iterator iter;
+ WallpaperData wp;
+
+ // Wallpaper filename container
+ std::vector<std::string> wallPaperVector;
+
+ for (iter = wallpaperData.begin(); iter != wallpaperData.end(); iter++)
+ {
+ wp = *iter;
+ if (wp.width <= width && wp.height <= height)
+ wallPaperVector.push_back(wp.filename);
+ }
+
+ if (!wallPaperVector.empty())
+ {
+ // If we've got more than one occurence of a valid wallpaper...
+ if (wallPaperVector.size() > 0)
+ {
+ // Return randomly a wallpaper between vector[0] and
+ // vector[vector.size() - 1]
+ srand((unsigned) time(0));
+ return wallPaperVector[int(static_cast<double>(
+ wallPaperVector.size()) * rand() / (RAND_MAX + 1.0))];
+ }
+ else // If there at least one, we return it
+ {
+ return wallPaperVector[0];
+ }
+ }
+
+ // Return the backup file if everything else failed...
+ if (haveBackup)
+ return std::string(wallpaperPath + wallpaperFile);
+
+ // Return an empty string if everything else failed
+ return std::string();
+}
diff --git a/src/resources/wallpaper.h b/src/resources/wallpaper.h
new file mode 100644
index 000000000..bb640d1ef
--- /dev/null
+++ b/src/resources/wallpaper.h
@@ -0,0 +1,50 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WALLPAPER_H
+#define WALLPAPER_H
+
+#include <string>
+
+/**
+ * Handles organizing and choosing of wallpapers.
+ */
+class Wallpaper
+{
+ public:
+ /**
+ * Reads the folder that contains wallpapers and organizes the
+ * wallpapers found by area, width, and height.
+ */
+ static void loadWallpapers();
+
+ /**
+ * Returns the largest wallpaper for the given resolution, or the
+ * default wallpaper if none are found.
+ *
+ * @param width the desired width
+ * @param height the desired height
+ * @return the file to use, or empty if no wallpapers are useable
+ */
+ static std::string getWallpaper(int width, int height);
+};
+
+#endif // WALLPAPER_H