diff options
author | Andrei Karas <akaras@inbox.ru> | 2011-01-02 01:48:38 +0200 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2011-01-02 02:41:24 +0200 |
commit | 3eeae12c498d1a4dbe969462d2ba841f77ee3ccb (patch) | |
tree | ff8eab35e732bc0749fc11677c8873a7b3a58704 /src/resources | |
download | plus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.gz plus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.bz2 plus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.tar.xz plus-3eeae12c498d1a4dbe969462d2ba841f77ee3ccb.zip |
Initial commit.
This code based on mana client http://www.gitorious.org/mana/mana
and my private repository.
Diffstat (limited to 'src/resources')
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 |