From 5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829 Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Sat, 31 Aug 2013 21:54:52 +0300 Subject: move particles into particle dir. --- src/particle/animationparticle.cpp | 61 ++++ src/particle/animationparticle.h | 52 +++ src/particle/imageparticle.cpp | 110 ++++++ src/particle/imageparticle.h | 68 ++++ src/particle/particle.cpp | 478 +++++++++++++++++++++++++ src/particle/particle.h | 366 ++++++++++++++++++++ src/particle/particlecontainer.cpp | 196 +++++++++++ src/particle/particlecontainer.h | 138 ++++++++ src/particle/particleemitter.cpp | 674 ++++++++++++++++++++++++++++++++++++ src/particle/particleemitter.h | 167 +++++++++ src/particle/particleemitterprop.h | 132 +++++++ src/particle/rotationalparticle.cpp | 96 +++++ src/particle/rotationalparticle.h | 52 +++ src/particle/textparticle.cpp | 89 +++++ src/particle/textparticle.h | 62 ++++ 15 files changed, 2741 insertions(+) create mode 100644 src/particle/animationparticle.cpp create mode 100644 src/particle/animationparticle.h create mode 100644 src/particle/imageparticle.cpp create mode 100644 src/particle/imageparticle.h create mode 100644 src/particle/particle.cpp create mode 100644 src/particle/particle.h create mode 100644 src/particle/particlecontainer.cpp create mode 100644 src/particle/particlecontainer.h create mode 100644 src/particle/particleemitter.cpp create mode 100644 src/particle/particleemitter.h create mode 100644 src/particle/particleemitterprop.h create mode 100644 src/particle/rotationalparticle.cpp create mode 100644 src/particle/rotationalparticle.h create mode 100644 src/particle/textparticle.cpp create mode 100644 src/particle/textparticle.h (limited to 'src/particle') diff --git a/src/particle/animationparticle.cpp b/src/particle/animationparticle.cpp new file mode 100644 index 000000000..bc88e5d95 --- /dev/null +++ b/src/particle/animationparticle.cpp @@ -0,0 +1,61 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/animationparticle.h" + +#include "simpleanimation.h" + +#include "render/graphics.h" + +#include "debug.h" + +AnimationParticle::AnimationParticle(Map *const map, + Animation *const animation) : + ImageParticle(map, nullptr), + mAnimation(new SimpleAnimation(animation)) +{ +} + +AnimationParticle::AnimationParticle(Map *const map, + XmlNodePtr const animationNode, + const std::string& dyePalettes): + ImageParticle(map, nullptr), + mAnimation(new SimpleAnimation(animationNode, dyePalettes)) +{ +} + +AnimationParticle::~AnimationParticle() +{ + delete mAnimation; + mAnimation = nullptr; + mImage = nullptr; +} + +bool AnimationParticle::update() +{ + if (mAnimation) + { + mAnimation->update(10); // particle engine is updated every 10ms + mImage = mAnimation->getCurrentImage(); + } + return Particle::update(); +} diff --git a/src/particle/animationparticle.h b/src/particle/animationparticle.h new file mode 100644 index 000000000..182bfa177 --- /dev/null +++ b/src/particle/animationparticle.h @@ -0,0 +1,52 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_ANIMATIONPARTICLE_H +#define PARTICLE_ANIMATIONPARTICLE_H + +#include "particle/imageparticle.h" + +#include "utils/xml.h" + +class Animation; +class Map; +class SimpleAnimation; + +class AnimationParticle final : public ImageParticle +{ + public: + AnimationParticle(Map *const map, Animation *const animation); + + AnimationParticle(Map *const map, XmlNodePtr const animationNode, + const std::string& dyePalettes = std::string()); + + A_DELETE_COPY(AnimationParticle) + + ~AnimationParticle(); + + virtual bool update() override; + + private: + SimpleAnimation *mAnimation; /**< Used animation for this particle */ +}; + +#endif // PARTICLE_ANIMATIONPARTICLE_H diff --git a/src/particle/imageparticle.cpp b/src/particle/imageparticle.cpp new file mode 100644 index 000000000..b8fd5bc94 --- /dev/null +++ b/src/particle/imageparticle.cpp @@ -0,0 +1,110 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/imageparticle.h" + +#include "logger.h" + +#include "render/graphics.h" + +#include "resources/image.h" + +#include "debug.h" + +std::map ImageParticle::imageParticleCountByName; + +ImageParticle::ImageParticle(Map *const map, Image *const image): + Particle(map), + mImage(image) +{ + if (mImage) + { + mImage->incRef(); + + const std::string &name = mImage->getIdPath(); + std::map::iterator it + = ImageParticle::imageParticleCountByName.find(name); + if (it == ImageParticle::imageParticleCountByName.end()) + ImageParticle::imageParticleCountByName[name] = 1; + else + (*it).second ++; + } +} + +ImageParticle::~ImageParticle() +{ + if (mImage) + { + const std::string &name = mImage->getIdPath(); + std::map::iterator it + = ImageParticle::imageParticleCountByName.find(name); + if (it != ImageParticle::imageParticleCountByName.end()) + { + int &cnt = (*it).second; + if (cnt > 0) + cnt --; + } + + mImage->decRef(); + mImage = nullptr; + } + setMap(nullptr); +} + +bool ImageParticle::draw(Graphics *const graphics, + const int offsetX, const int offsetY) const +{ + FUNC_BLOCK("ImageParticle::draw", 1) + if (mAlive != ALIVE || !mImage) + return false; + + const int screenX = static_cast(mPos.x) + + offsetX - mImage->mBounds.w / 2; + const int screenY = static_cast(mPos.y) - static_cast(mPos.z) + + offsetY - mImage->mBounds.h / 2; + + // Check if on screen + if (screenX + mImage->mBounds.w < 0 || + screenX > graphics->mWidth || + screenY + mImage->mBounds.h < 0 || + screenY > graphics->mHeight) + { + return false; + } + + float alphafactor = mAlpha; + + if (mFadeOut && mLifetimeLeft > -1 && mLifetimeLeft < mFadeOut) + { + alphafactor *= static_cast(mLifetimeLeft) + / static_cast(mFadeOut); + } + + if (mFadeIn && mLifetimePast < mFadeIn) + { + alphafactor *= static_cast(mLifetimePast) + / static_cast(mFadeIn); + } + + mImage->setAlpha(alphafactor); + return graphics->drawImage(mImage, screenX, screenY); +} diff --git a/src/particle/imageparticle.h b/src/particle/imageparticle.h new file mode 100644 index 000000000..9217417fd --- /dev/null +++ b/src/particle/imageparticle.h @@ -0,0 +1,68 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_IMAGEPARTICLE_H +#define PARTICLE_IMAGEPARTICLE_H + +#include "particle/particle.h" + +#include + +class Image; +class Map; + +/** + * A particle that uses an image for its visualization. + */ +class ImageParticle : public Particle +{ + public: + /** + * Constructor. The image is reference counted by this particle. + * + * @param map the map this particle appears on + * @param image an Image instance, may not be NULL + */ + ImageParticle(Map *const map, Image *const image); + + A_DELETE_COPY(ImageParticle) + + /** + * Destructor. + */ + ~ImageParticle(); + + /** + * Draws the particle image + */ + virtual bool draw(Graphics *const graphics, + const int offsetX, const int offsetY) const override; + + virtual void setAlpha(const float alpha) override + { mAlpha = alpha; } + + static std::map imageParticleCountByName; + protected: + Image *mImage; /**< The image used for this particle. */ +}; + +#endif // PARTICLE_IMAGEPARTICLE_H diff --git a/src/particle/particle.cpp b/src/particle/particle.cpp new file mode 100644 index 000000000..fa07fb235 --- /dev/null +++ b/src/particle/particle.cpp @@ -0,0 +1,478 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/particle.h" + +#include "configuration.h" +#include "resources/dye.h" +#include "logger.h" +#include "map.h" + +#include "particle/animationparticle.h" +#include "particle/particleemitter.h" +#include "particle/rotationalparticle.h" +#include "particle/textparticle.h" + +#include "resources/resourcemanager.h" + +#include "gui/sdlfont.h" + +#include "utils/dtor.h" +#include "utils/mathutils.h" + +#include + +#include +#include + +#include "debug.h" + +static const float SIN45 = 0.707106781f; + +class Graphics; +class Image; + +int Particle::particleCount = 0; +int Particle::maxCount = 0; +int Particle::fastPhysics = 0; +int Particle::emitterSkip = 1; +bool Particle::enabled = true; +const float Particle::PARTICLE_SKY = 800.0f; + +Particle::Particle(Map *const map) : + Actor(), + mAlpha(1.0f), + mLifetimeLeft(-1), + mLifetimePast(0), + mFadeOut(0), + mFadeIn(0), + mVelocity(), + mAlive(ALIVE), + mChildEmitters(), + mChildParticles(), + mDeathEffect(), + mGravity(0.0f), + mBounce(0.0f), + mAcceleration(0.0f), + mInvDieDistance(-1.0f), + mMomentum(1.0f), + mTarget(nullptr), + mRandomness(0), + mDeathEffectConditions(0x00), + mAutoDelete(true), + mAllowSizeAdjust(false), + mFollow(false) +{ + setMap(map); + Particle::particleCount++; +} + +Particle::~Particle() +{ + // Delete child emitters and child particles + clear(); + Particle::particleCount--; +} + +void Particle::setupEngine() +{ + Particle::maxCount = config.getIntValue("particleMaxCount"); + Particle::fastPhysics = config.getIntValue("particleFastPhysics"); + Particle::emitterSkip = config.getIntValue("particleEmitterSkip") + 1; + if (!Particle::emitterSkip) + Particle::emitterSkip = 1; + Particle::enabled = config.getBoolValue("particleeffects"); + disableAutoDelete(); + logger->log1("Particle engine set up"); +} + +bool Particle::draw(Graphics *const, const int, const int) const +{ + return false; +} + +bool Particle::update() +{ + if (!mMap) + return false; + + if (mLifetimeLeft == 0 && mAlive == ALIVE) + mAlive = DEAD_TIMEOUT; + + const Vector oldPos = mPos; + + if (mAlive == ALIVE) + { + // calculate particle movement + if (mMomentum != 1.0f) + mVelocity *= mMomentum; + + if (mTarget && mAcceleration != 0.0f) + { + Vector dist = mPos - mTarget->mPos; + dist.x *= SIN45; + float invHypotenuse; + + switch (Particle::fastPhysics) + { + case 1: + invHypotenuse = fastInvSqrt( + dist.x * dist.x + dist.y * dist.y + dist.z * dist.z); + break; + case 2: + if (!dist.x) + { + invHypotenuse = 0; + break; + } + + invHypotenuse = 2.0f / (static_cast(fabs(dist.x)) + + static_cast(fabs(dist.y)) + + static_cast(fabs(dist.z))); + break; + default: + invHypotenuse = 1.0f / static_cast(sqrt( + dist.x * dist.x + dist.y * dist.y + dist.z * dist.z)); + break; + } + + if (invHypotenuse) + { + if (mInvDieDistance > 0.0f && invHypotenuse > mInvDieDistance) + mAlive = DEAD_IMPACT; + const float accFactor = invHypotenuse * mAcceleration; + mVelocity -= dist * accFactor; + } + } + + if (mRandomness > 0) + { + mVelocity.x += static_cast((rand() % mRandomness - rand() + % mRandomness)) / 1000.0f; + mVelocity.y += static_cast((rand() % mRandomness - rand() + % mRandomness)) / 1000.0f; + mVelocity.z += static_cast((rand() % mRandomness - rand() + % mRandomness)) / 1000.0f; + } + + mVelocity.z -= mGravity; + + // Update position + mPos.x += mVelocity.x; + mPos.y += mVelocity.y * SIN45; + mPos.z += mVelocity.z * SIN45; + + // Update other stuff + if (mLifetimeLeft > 0) + mLifetimeLeft--; + + mLifetimePast++; + + if (mPos.z < 0.0f) + { + if (mBounce > 0.0f) + { + mPos.z *= -mBounce; + mVelocity *= mBounce; + mVelocity.z = -mVelocity.z; + } + else + { + mAlive = DEAD_FLOOR; + } + } + else if (mPos.z > PARTICLE_SKY) + { + mAlive = DEAD_SKY; + } + + // Update child emitters + if (Particle::emitterSkip && (mLifetimePast - 1) + % Particle::emitterSkip == 0) + { + FOR_EACH (EmitterConstIterator, e, mChildEmitters) + { + Particles newParticles = (*e)->createParticles(mLifetimePast); + FOR_EACH (ParticleConstIterator, it, newParticles) + { + Particle *const p = *it; + p->moveBy(mPos); + mChildParticles.push_back(p); + } + } + } + } + + // create death effect when the particle died + if (mAlive != ALIVE && mAlive != DEAD_LONG_AGO) + { + if ((mAlive & mDeathEffectConditions) > 0x00 && !mDeathEffect.empty()) + { + Particle *const deathEffect = particleEngine->addEffect( + mDeathEffect, 0, 0); + if (deathEffect) + deathEffect->moveBy(mPos); + } + mAlive = DEAD_LONG_AGO; + } + + const Vector change = mPos - oldPos; + + // Update child particles + + for (ParticleIterator p = mChildParticles.begin(), + p2 = mChildParticles.end(); p != p2; ) + { + Particle *const particle = *p; + // move particle with its parent if desired + if (particle->mFollow) + particle->moveBy(change); + + // update particle + if (particle->update()) + { + ++p; + } + else + { + delete particle; + p = mChildParticles.erase(p); + } + } + if (mAlive != ALIVE && mChildParticles.empty() && mAutoDelete) + return false; + + return true; +} + +void Particle::moveBy(const Vector &change) +{ + mPos += change; + FOR_EACH (ParticleConstIterator, p, mChildParticles) + { + Particle *const particle = *p; + if (particle->mFollow) + particle->moveBy(change); + } +} + +void Particle::moveTo(const float x, const float y) +{ + moveTo(Vector(x, y, mPos.z)); +} + +Particle *Particle::createChild() +{ + Particle *const newParticle = new Particle(mMap); + mChildParticles.push_back(newParticle); + return newParticle; +} + +Particle *Particle::addEffect(const std::string &particleEffectFile, + const int pixelX, const int pixelY, + const int rotation) +{ + Particle *newParticle = nullptr; + + const size_t pos = particleEffectFile.find('|'); + const std::string dyePalettes = (pos != std::string::npos) + ? particleEffectFile.substr(pos + 1) : ""; + XML::Document doc(particleEffectFile.substr(0, pos)); + const XmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlNameEqual(rootNode, "effect")) + { + logger->log("Error loading particle: %s", particleEffectFile.c_str()); + return nullptr; + } + + ResourceManager *const resman = ResourceManager::getInstance(); + + // Parse particles + for_each_xml_child_node(effectChildNode, rootNode) + { + // We're only interested in particles + if (!xmlNameEqual(effectChildNode, "particle")) + continue; + + // Determine the exact particle type + XmlNodePtr node; + + // Animation + if ((node = XML::findFirstChildByName(effectChildNode, "animation"))) + { + newParticle = new AnimationParticle(mMap, node, dyePalettes); + } + // Rotational + else if ((node = XML::findFirstChildByName( + effectChildNode, "rotation"))) + { + newParticle = new RotationalParticle(mMap, node, dyePalettes); + } + // Image + else if ((node = XML::findFirstChildByName(effectChildNode, "image"))) + { + std::string imageSrc = reinterpret_cast( + node->xmlChildrenNode->content); + if (!imageSrc.empty() && !dyePalettes.empty()) + Dye::instantiate(imageSrc, dyePalettes); + Image *const img = resman->getImage(imageSrc); + + newParticle = new ImageParticle(mMap, img); + } + // Other + else + { + newParticle = new Particle(mMap); + } + + // Read and set the basic properties of the particle + const float offsetX = static_cast(XML::getFloatProperty( + effectChildNode, "position-x", 0)); + const float offsetY = static_cast(XML::getFloatProperty( + effectChildNode, "position-y", 0)); + const float offsetZ = static_cast(XML::getFloatProperty( + effectChildNode, "position-z", 0)); + const Vector position(mPos.x + static_cast(pixelX) + offsetX, + mPos.y + static_cast(pixelY) + offsetY, + mPos.z + offsetZ); + newParticle->moveTo(position); + + const int lifetime = XML::getProperty(effectChildNode, "lifetime", -1); + newParticle->setLifetime(lifetime); + const bool resizeable = "false" != XML::getProperty(effectChildNode, + "size-adjustable", "false"); + + newParticle->setAllowSizeAdjust(resizeable); + + // Look for additional emitters for this particle + for_each_xml_child_node(emitterNode, effectChildNode) + { + if (xmlNameEqual(emitterNode, "emitter")) + { + ParticleEmitter *const newEmitter = new ParticleEmitter( + emitterNode, newParticle, mMap, rotation, dyePalettes); + newParticle->addEmitter(newEmitter); + } + else if (xmlNameEqual(emitterNode, "deatheffect")) + { + const std::string deathEffect = reinterpret_cast( + emitterNode->xmlChildrenNode->content); + + char deathEffectConditions = 0x00; + if (XML::getBoolProperty(emitterNode, "on-floor", true)) + { + deathEffectConditions += static_cast( + Particle::DEAD_FLOOR); + } + if (XML::getBoolProperty(emitterNode, "on-sky", true)) + { + deathEffectConditions += static_cast( + Particle::DEAD_SKY); + } + if (XML::getBoolProperty(emitterNode, "on-other", false)) + { + deathEffectConditions += static_cast( + Particle::DEAD_OTHER); + } + if (XML::getBoolProperty(emitterNode, "on-impact", true)) + { + deathEffectConditions += static_cast( + Particle::DEAD_IMPACT); + } + if (XML::getBoolProperty(emitterNode, "on-timeout", true)) + { + deathEffectConditions += static_cast( + Particle::DEAD_TIMEOUT); + } + newParticle->setDeathEffect( + deathEffect, deathEffectConditions); + } + } + + mChildParticles.push_back(newParticle); + } + + return newParticle; +} + +Particle *Particle::addTextSplashEffect(const std::string &text, + const int x, const int y, + const gcn::Color *const color, + gcn::Font *const font, + const bool outline) +{ + Particle *const newParticle = new TextParticle( + mMap, text, color, font, outline); + newParticle->moveTo(static_cast(x), static_cast(y)); + newParticle->setVelocity( + static_cast((rand() % 100) - 50) / 200.0f, // X + static_cast((rand() % 100) - 50) / 200.0f, // Y + (static_cast((rand() % 100)) / 200.0f) + 4.0f); // Z + + newParticle->setGravity(0.1f); + newParticle->setBounce(0.5f); + newParticle->setLifetime(200); + newParticle->setFadeOut(100); + + mChildParticles.push_back(newParticle); + + return newParticle; +} + +Particle *Particle::addTextRiseFadeOutEffect(const std::string &text, + const int x, const int y, + const gcn::Color *const color, + gcn::Font *const font, + const bool outline) +{ + Particle *const newParticle = new TextParticle( + mMap, text, color, font, outline); + newParticle->moveTo(static_cast(x), static_cast(y)); + newParticle->setVelocity(0.0f, 0.0f, 0.5f); + newParticle->setGravity(0.0015f); + newParticle->setLifetime(300); + newParticle->setFadeOut(100); + newParticle->setFadeIn(0); + + mChildParticles.push_back(newParticle); + + return newParticle; +} + +void Particle::adjustEmitterSize(const int w, const int h) +{ + if (mAllowSizeAdjust) + { + FOR_EACH (EmitterConstIterator, e, mChildEmitters) + (*e)->adjustSize(w, h); + } +} + +void Particle::clear() +{ + delete_all(mChildEmitters); + mChildEmitters.clear(); + + delete_all(mChildParticles); + mChildParticles.clear(); +} diff --git a/src/particle/particle.h b/src/particle/particle.h new file mode 100644 index 000000000..29cc7c0b4 --- /dev/null +++ b/src/particle/particle.h @@ -0,0 +1,366 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_PARTICLE_H +#define PARTICLE_PARTICLE_H + +#include "actor.h" +#include "localconsts.h" + +#include +#include + +class Map; +class Particle; +class ParticleEmitter; +class SDLFont; + +namespace gcn +{ + class Color; + class Font; +} + +typedef std::list Particles; +typedef Particles::iterator ParticleIterator; +typedef Particles::const_iterator ParticleConstIterator; +typedef std::list Emitters; +typedef Emitters::iterator EmitterIterator; +typedef Emitters::const_iterator EmitterConstIterator; + +/** + * A particle spawned by a ParticleEmitter. + */ +class Particle : public Actor +{ + public: + enum AliveStatus + { + ALIVE = 0, + DEAD_TIMEOUT = 1, + DEAD_FLOOR = 2, + DEAD_SKY = 4, + DEAD_IMPACT = 8, + DEAD_OTHER = 16, + DEAD_LONG_AGO = 128 + }; + static const float PARTICLE_SKY; // Maximum Z position of particles + static int fastPhysics; // Mode of squareroot calculation + static int particleCount; // Current number of particles + static int maxCount; // Maximum number of particles + static int emitterSkip; // Duration of pause between two + // emitter updates in ticks + static bool enabled; // true when non-crucial particle effects + // are disabled + + /** + * Constructor. + * + * @param map the map this particle will add itself to, may be nullptr + */ + explicit Particle(Map *const map); + + A_DELETE_COPY(Particle) + + /** + * Destructor. + */ + ~Particle(); + + /** + * Deletes all child particles and emitters. + */ + void clear(); + + /** + * Gives a particle the properties of an engine root particle and loads + * the particle-related config settings. + */ + void setupEngine(); + + /** + * Updates particle position, returns false when the particle should + * be deleted. + */ + virtual bool update(); + + /** + * Draws the particle image. + */ + virtual bool draw(Graphics *const graphics, + const int offsetX, const int offsetY) const override; + + /** + * Necessary for sorting with the other sprites. + */ + virtual int getPixelY() const override A_WARN_UNUSED + { return static_cast(mPos.y) - 16; } + + /** + * Necessary for sorting with the other sprites for sorting only. + */ + virtual int getSortPixelY() const override A_WARN_UNUSED + { return static_cast(mPos.y) - 16; } + + /** + * Creates a blank particle as a child of the current particle + * Useful for creating target particles + */ + Particle *createChild(); + + /** + * Creates a child particle that hosts some emitters described in the + * particleEffectFile. + */ + Particle *addEffect(const std::string &particleEffectFile, + const int pixelX, const int pixelY, + const int rotation = 0); + + /** + * Creates a standalone text particle. + */ + Particle *addTextSplashEffect(const std::string &text, + const int x, const int y, + const gcn::Color *const color, + gcn::Font *const font, + const bool outline = false); + + /** + * Creates a standalone text particle. + */ + Particle *addTextRiseFadeOutEffect(const std::string &text, + const int x, const int y, + const gcn::Color *const color, + gcn::Font *const font, + const bool outline = false); + + /** + * Adds an emitter to the particle. + */ + void addEmitter(ParticleEmitter *const emitter) + { mChildEmitters.push_back(emitter); } + + /** + * Sets the position in 3 dimensional space in pixels relative to map. + */ + void moveTo(const Vector &pos) + { moveBy(pos - mPos); } + + /** + * Sets the position in 2 dimensional space in pixels relative to map. + */ + void moveTo(const float x, const float y); + + /** + * Changes the particle position relative + */ + void moveBy(const Vector &change); + + /** + * Sets the time in game ticks until the particle is destroyed. + */ + void setLifetime(const int lifetime) + { mLifetimeLeft = lifetime; mLifetimePast = 0; } + + /** + * Sets the age of the pixel in game ticks where the particle has + * faded in completely. + */ + void setFadeOut(const int fadeOut) + { mFadeOut = fadeOut; } + + /** + * Sets the remaining particle lifetime where the particle starts to + * fade out. + */ + void setFadeIn(const int fadeIn) + { mFadeIn = fadeIn; } + + /** + * Sets the current velocity in 3 dimensional space. + */ + void setVelocity(const float x, const float y, const float z) + { mVelocity.x = x; mVelocity.y = y; mVelocity.z = z; } + + /** + * Sets the downward acceleration. + */ + void setGravity(const float gravity) + { mGravity = gravity; } + + /** + * Sets the ammount of random vector changes + */ + void setRandomness(const int r) + { mRandomness = r; } + + /** + * Sets the ammount of velocity particles retain after + * hitting the ground. + */ + void setBounce(const float bouncieness) + { mBounce = bouncieness; } + + /** + * Sets the flag if the particle is supposed to be moved by its parent + */ + void setFollow(const bool follow) + { mFollow = follow; } + + /** + * Gets the flag if the particle is supposed to be moved by its parent + */ + bool doesFollow() const A_WARN_UNUSED + { return mFollow; } + + /** + * Makes the particle move toward another particle with a + * given acceleration and momentum + */ + void setDestination(Particle *const target, + const float accel, const float moment) + { mTarget = target; mAcceleration = accel; mMomentum = moment; } + + /** + * Sets the distance in pixel the particle can come near the target + * particle before it is destroyed. Does only make sense after a target + * particle has been set using setDestination. + */ + void setDieDistance(const float dist) + { mInvDieDistance = 1.0f / dist; } + + /** + * Changes the size of the emitters so that the effect fills a + * rectangle of this size + */ + void adjustEmitterSize(const int w, const int h); + + void setAllowSizeAdjust(const bool adjust) + { mAllowSizeAdjust = adjust; } + + bool isAlive() const A_WARN_UNUSED + { return mAlive == ALIVE; } + + /** + * Determines whether the particle and its children are all dead + */ + bool isExtinct() const A_WARN_UNUSED + { return !isAlive() && mChildParticles.empty(); } + + /** + * Manually marks the particle for deletion. + */ + void kill() + { mAlive = DEAD_OTHER; mAutoDelete = true; } + + /** + * After calling this function the particle will only request + * deletion when kill() is called + */ + void disableAutoDelete() + { mAutoDelete = false; } + + /** We consider particles (at least for now) to be one layer-sprites */ + virtual int getNumberOfLayers() const + { return 1; } + + virtual float getAlpha() const + { return 1.0f; } + + virtual void setAlpha(const float alpha A_UNUSED) override + { } + + virtual void setDeathEffect(const std::string &effectFile, + const signed char conditions) + { mDeathEffect = effectFile; mDeathEffectConditions = conditions; } + + protected: + // Opacity of the graphical representation of the particle + float mAlpha; + + // Lifetime left in game ticks + int mLifetimeLeft; + + // Age of the particle in game ticks + int mLifetimePast; + + // Lifetime in game ticks left where fading out begins + int mFadeOut; + + // Age in game ticks where fading in is finished + int mFadeIn; + + // Speed in pixels per game-tick. + Vector mVelocity; + + // Is the particle supposed to be drawn and updated? + AliveStatus mAlive; + private: + // List of child emitters. + Emitters mChildEmitters; + + // List of particles controlled by this particle + Particles mChildParticles; + + // Particle effect file to be spawned when the particle dies + std::string mDeathEffect; + + // dynamic particle + // Downward acceleration in pixels per game-tick. + float mGravity; + + // How much the particle bounces off when hitting the ground + float mBounce; + + // Acceleration towards the target particle in pixels per game-tick + float mAcceleration; + + // Distance in pixels from the target particle that causes + // the destruction of the particle + float mInvDieDistance; + + // How much speed the particle retains after each game tick + float mMomentum; + + // The particle that attracts this particle + Particle *mTarget; + + // Ammount of random vector change + int mRandomness; + + // Bitfield of death conditions which trigger spawning + // of the death particle + signed char mDeathEffectConditions; + + // May the particle request its deletion by the parent particle? + bool mAutoDelete; + + // Can the effect size be adjusted by the object props in the map file? + bool mAllowSizeAdjust; + + // is this particle moved when its parent particle moves? + bool mFollow; +}; + +extern Particle *particleEngine; + +#endif // PARTICLE_PARTICLE_H diff --git a/src/particle/particlecontainer.cpp b/src/particle/particlecontainer.cpp new file mode 100644 index 000000000..1695d55f2 --- /dev/null +++ b/src/particle/particlecontainer.cpp @@ -0,0 +1,196 @@ +/* + * The ManaPlus Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/particle.h" +#include "particle/particlecontainer.h" + +#include "debug.h" + +typedef std::list::iterator ParticleListIter; +typedef std::list::const_iterator ParticleListCIter; + +ParticleContainer::ParticleContainer(ParticleContainer *const parent, + const bool delParent): + mNext(parent), + mDelParent(delParent) +{ +} + +ParticleContainer::~ParticleContainer() +{ + clearLocally(); + if (mDelParent) + { + delete mNext; + mNext = nullptr; + } +} + +void ParticleContainer::clear() +{ + clearLocally(); + if (mNext) + mNext->clear(); +} + +void ParticleContainer::moveTo(const float x, const float y) +{ + if (mNext) + mNext->moveTo(x, y); +} + +// -- particle list ---------------------------------------- + +ParticleList::ParticleList(ParticleContainer *const parent, + const bool delParent) : + ParticleContainer(parent, delParent), + mElements() +{} + +ParticleList::~ParticleList() +{} + +void ParticleList::addLocally(Particle *const particle) +{ + if (particle) + { + // The effect may not die without the beings permission or we segfault + particle->disableAutoDelete(); + mElements.push_back(particle); + } +} + +void ParticleList::removeLocally(const Particle *const particle) +{ + for (std::list::iterator it = mElements.begin(); + it != mElements.end(); ) + { + Particle *const p = *it; + if (p == particle) + { + p->kill(); + it = mElements.erase(it); + } + else + { + ++it; + } + } +} + +void ParticleList::clearLocally() +{ + FOR_EACH (ParticleListCIter, it, mElements) + (*it)->kill(); + + mElements.clear(); +} + +void ParticleList::moveTo(const float x, const float y) +{ + ParticleContainer::moveTo(x, y); + + for (std::list::iterator it = mElements.begin(); + it != mElements.end(); ) + { + Particle *const p = *it; + p->moveTo(x, y); + if (p->isExtinct()) + { + p->kill(); + it = mElements.erase(it); + } + else + { + ++it; + } + } +} + +// -- particle vector ---------------------------------------- + +ParticleVector::ParticleVector(ParticleContainer *const parent, + const bool delParent) : + ParticleContainer(parent, delParent), + mIndexedElements() +{} + +ParticleVector::~ParticleVector() +{} + +void ParticleVector::setLocally(const int index, Particle *const particle) +{ + if (index < 0) + return; + + delLocally(index); + + if (mIndexedElements.size() <= static_cast(index)) + mIndexedElements.resize(index + 1, nullptr); + + if (particle) + particle->disableAutoDelete(); + mIndexedElements[index] = particle; +} + +void ParticleVector::delLocally(const int index) +{ + if (index < 0) + return; + + if (mIndexedElements.size() <= static_cast(index)) + return; + + Particle *const p = mIndexedElements[index]; + if (p) + { + mIndexedElements[index] = nullptr; + p->kill(); + } +} + +void ParticleVector::clearLocally() +{ + for (unsigned int i = 0; i < mIndexedElements.size(); i++) + delLocally(i); +} + +void ParticleVector::moveTo(const float x, const float y) +{ + ParticleContainer::moveTo(x, y); + + for (std::vector::iterator it = mIndexedElements.begin(); + it != mIndexedElements.end(); ++it) + { + Particle *const p = *it; + if (p) + { + p->moveTo(x, y); + + if (p->isExtinct()) + { + p->kill(); + *it = nullptr; + } + } + } +} diff --git a/src/particle/particlecontainer.h b/src/particle/particlecontainer.h new file mode 100644 index 000000000..a348f4cef --- /dev/null +++ b/src/particle/particlecontainer.h @@ -0,0 +1,138 @@ +/* + * The ManaPlus Client + * Copyright (C) 2008-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_PARTICLECONTAINER_H +#define PARTICLE_PARTICLECONTAINER_H + +#include +#include + +#include "localconsts.h" + +class Particle; + +/** + * Set of particle effects. May be stacked with other ParticleContainers. All + * operations herein affect such stacked containers, unless the operations end + * in `Locally'. + */ +class ParticleContainer +{ +public: + /** + * Constructs a new particle container and assumes responsibility for + * its parent (for all operations defined herein, except when ending in `Locally') + * + * delParent means that the destructor should also free the parent. + */ + explicit ParticleContainer(ParticleContainer *const parent = nullptr, + const bool delParent = true); + + A_DELETE_COPY(ParticleContainer) + + virtual ~ParticleContainer(); + + /** + * Kills and removes all particle effects + */ + void clear(); + + /** + * Kills and removes all particle effects (only in this container) + */ + virtual void clearLocally() + { } + + /** + * Sets the positions of all elements + */ + virtual void moveTo(const float x, const float y); + +protected: + ParticleContainer *mNext; /**< Contained container, if any */ + bool mDelParent; /**< Delete mNext in destructor */ +}; + +/** + * Linked list of particle effects. + */ +class ParticleList final : public ParticleContainer +{ +public: + explicit ParticleList(ParticleContainer *const parent = nullptr, + const bool delParent = true); + + A_DELETE_COPY(ParticleList) + + virtual ~ParticleList(); + + /** + * Takes control of and adds a particle + */ + void addLocally(Particle *const particle); + + /** + * `kills' and removes a particle + */ + void removeLocally(const Particle *const particle); + + virtual void clearLocally() override; + + virtual void moveTo(const float x, const float y) override; + +protected: + std::list mElements; /**< Contained particle effects */ +}; + +/** + * Particle container with indexing facilities + */ +class ParticleVector final : public ParticleContainer +{ +public: + explicit ParticleVector(ParticleContainer *const parent = nullptr, + const bool delParent = true); + + A_DELETE_COPY(ParticleVector) + + virtual ~ParticleVector(); + + /** + * Sets a particle at a specified index. Kills the previous particle + * there, if needed. + */ + virtual void setLocally(const int index, Particle *const particle); + + /** + * Removes a particle at a specified index + */ + virtual void delLocally(const int index); + + virtual void clearLocally() override; + + virtual void moveTo(const float x, const float y) override; + +protected: + std::vector mIndexedElements; +}; + +#endif // PARTICLE_PARTICLECONTAINER_H diff --git a/src/particle/particleemitter.cpp b/src/particle/particleemitter.cpp new file mode 100644 index 000000000..9e273e4a1 --- /dev/null +++ b/src/particle/particleemitter.cpp @@ -0,0 +1,674 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/particleemitter.h" + +#include "logger.h" + +#include "particle/animationparticle.h" +#include "particle/rotationalparticle.h" + +#include "resources/dye.h" +#include "resources/image.h" +#include "resources/imageset.h" +#include "resources/resourcemanager.h" + +#include + +#include "debug.h" + +static const float SIN45 = 0.707106781f; +static const float DEG_RAD_FACTOR = 0.017453293f; + +typedef std::vector::const_iterator ImageSetVectorCIter; +typedef std::list::const_iterator ParticleEmitterListCIter; + +ParticleEmitter::ParticleEmitter(const XmlNodePtr emitterNode, + Particle *const target, + Map *const map, const int rotation, + const std::string& dyePalettes) : + mParticleTarget(target), + mMap(map), + mParticleImage(nullptr), + mOutputPauseLeft(0), + mDeathEffectConditions(0), + mParticleFollow(false) +{ + // Initializing default values + mParticlePosX.set(0.0f); + mParticlePosY.set(0.0f); + mParticlePosZ.set(0.0f); + mParticleAngleHorizontal.set(0.0f); + mParticleAngleVertical.set(0.0f); + mParticlePower.set(0.0f); + mParticleGravity.set(0.0f); + mParticleRandomness.set(0); + mParticleBounce.set(0.0f); + mParticleAcceleration.set(0.0f); + mParticleDieDistance.set(-1.0f); + mParticleMomentum.set(1.0f); + mParticleLifetime.set(-1); + mParticleFadeOut.set(0); + mParticleFadeIn.set(0); + mOutput.set(1); + mOutputPause.set(0); + mParticleAlpha.set(1.0f); + + for_each_xml_child_node(propertyNode, emitterNode) + { + if (xmlNameEqual(propertyNode, "property")) + { + const std::string name = XML::getProperty( + propertyNode, "name", ""); + + if (name == "position-x") + { + mParticlePosX = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "position-y") + { + mParticlePosY = readParticleEmitterProp(propertyNode, 0.0f); + mParticlePosY.minVal *= SIN45; + mParticlePosY.maxVal *= SIN45; + mParticlePosY.changeAmplitude *= SIN45; + } + else if (name == "position-z") + { + mParticlePosZ = readParticleEmitterProp(propertyNode, 0.0f); + mParticlePosZ.minVal *= SIN45; + mParticlePosZ.maxVal *= SIN45; + mParticlePosZ.changeAmplitude *= SIN45; + } + else if (name == "image") + { + std::string image = XML::getProperty( + propertyNode, "value", ""); + // Don't leak when multiple images are defined + if (!image.empty() && !mParticleImage) + { + if (!dyePalettes.empty()) + Dye::instantiate(image, dyePalettes); + + ResourceManager *const resman + = ResourceManager::getInstance(); + mParticleImage = resman->getImage(image); + } + } + else if (name == "subimage") + { + std::string image = XML::getProperty( + propertyNode, "value", ""); + // Don't leak when multiple images are defined + if (!image.empty() && !mParticleImage) + { + if (!dyePalettes.empty()) + Dye::instantiate(image, dyePalettes); + + ResourceManager *const resman + = ResourceManager::getInstance(); + Image *img = resman->getImage(image); + if (img) + { + mParticleImage = resman->getSubImage(img, + XML::getProperty(propertyNode, "x", 0), + XML::getProperty(propertyNode, "y", 0), + XML::getProperty(propertyNode, "width", 0), + XML::getProperty(propertyNode, "height", 0)); + img->decRef(); + } + else + { + mParticleImage = nullptr; + } + } + } + else if (name == "horizontal-angle") + { + mParticleAngleHorizontal = + readParticleEmitterProp(propertyNode, 0.0f); + mParticleAngleHorizontal.minVal + += static_cast(rotation); + mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR; + mParticleAngleHorizontal.maxVal + += static_cast(rotation); + mParticleAngleHorizontal.maxVal *= DEG_RAD_FACTOR; + mParticleAngleHorizontal.changeAmplitude *= DEG_RAD_FACTOR; + } + else if (name == "vertical-angle") + { + mParticleAngleVertical = + readParticleEmitterProp(propertyNode, 0.0f); + mParticleAngleVertical.minVal *= DEG_RAD_FACTOR; + mParticleAngleVertical.maxVal *= DEG_RAD_FACTOR; + mParticleAngleVertical.changeAmplitude *= DEG_RAD_FACTOR; + } + else if (name == "power") + { + mParticlePower = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "gravity") + { + mParticleGravity = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "randomnes" + || name == "randomness") // legacy bug + { + mParticleRandomness = readParticleEmitterProp(propertyNode, 0); + } + else if (name == "bounce") + { + mParticleBounce = readParticleEmitterProp(propertyNode, 0.0f); + } + else if (name == "lifetime") + { + mParticleLifetime = readParticleEmitterProp(propertyNode, 0); + mParticleLifetime.minVal += 1; + } + else if (name == "output") + { + mOutput = readParticleEmitterProp(propertyNode, 0); + mOutput.maxVal += 1; + } + else if (name == "output-pause") + { + mOutputPause = readParticleEmitterProp(propertyNode, 0); + mOutputPauseLeft = mOutputPause.value(0); + } + else if (name == "acceleration") + { + mParticleAcceleration = readParticleEmitterProp( + propertyNode, 0.0f); + } + else if (name == "die-distance") + { + mParticleDieDistance = readParticleEmitterProp( + propertyNode, 0.0f); + } + else if (name == "momentum") + { + mParticleMomentum = readParticleEmitterProp( + propertyNode, 1.0f); + } + else if (name == "fade-out") + { + mParticleFadeOut = readParticleEmitterProp(propertyNode, 0); + } + else if (name == "fade-in") + { + mParticleFadeIn = readParticleEmitterProp(propertyNode, 0); + } + else if (name == "alpha") + { + mParticleAlpha = readParticleEmitterProp(propertyNode, 1.0f); + } + else if (name == "follow-parent") + { + mParticleFollow = true; + } + else + { + logger->log("Particle Engine: Warning, " + "unknown emitter property \"%s\"", + name.c_str()); + } + } + else if (xmlNameEqual(propertyNode, "emitter")) + { + ParticleEmitter newEmitter(propertyNode, mParticleTarget, map, + rotation, dyePalettes); + mParticleChildEmitters.push_back(newEmitter); + } + else if (xmlNameEqual(propertyNode, "rotation")) + { + ImageSet *const imageset = getImageSet(propertyNode); + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + mTempSets.push_back(imageset); + + // Get animation frames + for_each_xml_child_node(frameNode, propertyNode) + { + const int delay = XML::getIntProperty( + frameNode, "delay", 0, 0, 100000); + const int offsetX = XML::getProperty(frameNode, "offsetX", 0) + - imageset->getWidth() / 2 + 16; + const int offsetY = XML::getProperty(frameNode, "offsetY", 0) + - imageset->getHeight() + 32; + const int rand = XML::getIntProperty( + frameNode, "rand", 100, 0, 100); + + if (xmlNameEqual(frameNode, "frame")) + { + const int index = XML::getProperty(frameNode, "index", -1); + + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *const img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + mParticleRotation.addFrame(img, delay, + offsetX, offsetY, rand); + } + else if (xmlNameEqual(frameNode, "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 *const img = imageset->get(start); + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + mParticleRotation.addFrame(img, delay, + offsetX, offsetY, rand); + start ++; + } + } + else if (xmlNameEqual(frameNode, "end")) + { + mParticleRotation.addTerminator(rand); + } + } // for frameNode + } + else if (xmlNameEqual(propertyNode, "animation")) + { + ImageSet *const imageset = getImageSet(propertyNode); + if (!imageset) + { + logger->log1("Error: no valid imageset"); + continue; + } + mTempSets.push_back(imageset); + + // Get animation frames + for_each_xml_child_node(frameNode, propertyNode) + { + const int delay = XML::getIntProperty( + frameNode, "delay", 0, 0, 100000); + const int offsetX = XML::getProperty(frameNode, "offsetX", 0) + - imageset->getWidth() / 2 + 16; + const int offsetY = XML::getProperty(frameNode, "offsetY", 0) + - imageset->getHeight() + 32; + const int rand = XML::getIntProperty( + frameNode, "rand", 100, 0, 100); + + if (xmlNameEqual(frameNode, "frame")) + { + const int index = XML::getProperty(frameNode, "index", -1); + if (index < 0) + { + logger->log1("No valid value for 'index'"); + continue; + } + + Image *const img = imageset->get(index); + + if (!img) + { + logger->log("No image at index %d", index); + continue; + } + + mParticleAnimation.addFrame(img, delay, + offsetX, offsetY, rand); + } + else if (xmlNameEqual(frameNode, "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 *const img = imageset->get(start); + + if (!img) + { + logger->log("No image at index %d", start); + continue; + } + + mParticleAnimation.addFrame(img, delay, + offsetX, offsetY, rand); + start++; + } + } + else if (xmlNameEqual(frameNode, "end")) + { + mParticleAnimation.addTerminator(rand); + } + } // for frameNode + } + else if (xmlNameEqual(propertyNode, "deatheffect")) + { + mDeathEffect = reinterpret_cast( + propertyNode->xmlChildrenNode->content); + mDeathEffectConditions = 0x00; + if (XML::getBoolProperty(propertyNode, "on-floor", true)) + { + mDeathEffectConditions += static_cast( + Particle::DEAD_FLOOR); + } + if (XML::getBoolProperty(propertyNode, "on-sky", true)) + { + mDeathEffectConditions += static_cast( + Particle::DEAD_SKY); + } + if (XML::getBoolProperty(propertyNode, "on-other", false)) + { + mDeathEffectConditions += static_cast( + Particle::DEAD_OTHER); + } + if (XML::getBoolProperty(propertyNode, "on-impact", true)) + { + mDeathEffectConditions += static_cast( + Particle::DEAD_IMPACT); + } + if (XML::getBoolProperty(propertyNode, "on-timeout", true)) + { + mDeathEffectConditions += static_cast( + Particle::DEAD_TIMEOUT); + } + } + } +} + +ParticleEmitter::ParticleEmitter(const ParticleEmitter &o) +{ + *this = o; +} + +ImageSet *ParticleEmitter::getImageSet(XmlNodePtr node) +{ + ResourceManager *const resman = ResourceManager::getInstance(); + ImageSet *imageset = nullptr; + const int subX = XML::getProperty(node, "subX", -1); + if (subX != -1) + { + Image *const img = resman->getImage(XML::getProperty( + node, "imageset", "")); + if (!img) + return nullptr; + + Image *const img2 = resman->getSubImage(img, subX, + XML::getProperty(node, "subY", 0), + XML::getProperty(node, "subWidth", 0), + XML::getProperty(node, "subHeight", 0)); + if (!img2) + { + img->decRef(); + return nullptr; + } + + imageset = resman->getSubImageSet(img2, + XML::getProperty(node, "width", 0), + XML::getProperty(node, "height", 0)); + img2->decRef(); + img->decRef(); + } + else + { + imageset = resman->getImageSet( + XML::getProperty(node, "imageset", ""), + XML::getProperty(node, "width", 0), + XML::getProperty(node, "height", 0)); + } + return imageset; +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter &o) +{ + mParticlePosX = o.mParticlePosX; + mParticlePosY = o.mParticlePosY; + mParticlePosZ = o.mParticlePosZ; + mParticleAngleHorizontal = o.mParticleAngleHorizontal; + mParticleAngleVertical = o.mParticleAngleVertical; + mParticlePower = o.mParticlePower; + mParticleGravity = o.mParticleGravity; + mParticleRandomness = o.mParticleRandomness; + mParticleBounce = o.mParticleBounce; + mParticleFollow = o.mParticleFollow; + mParticleTarget = o.mParticleTarget; + mParticleAcceleration = o.mParticleAcceleration; + mParticleDieDistance = o.mParticleDieDistance; + mParticleMomentum = o.mParticleMomentum; + mParticleLifetime = o.mParticleLifetime; + mParticleFadeOut = o.mParticleFadeOut; + mParticleFadeIn = o.mParticleFadeIn; + mParticleAlpha = o.mParticleAlpha; + mMap = o.mMap; + mOutput = o.mOutput; + mOutputPause = o.mOutputPause; + mParticleImage = o.mParticleImage; + mParticleAnimation = o.mParticleAnimation; + mParticleRotation = o.mParticleRotation; + mParticleChildEmitters = o.mParticleChildEmitters; + mDeathEffectConditions = o.mDeathEffectConditions; + mDeathEffect = o.mDeathEffect; + mTempSets = o.mTempSets; + + FOR_EACH (ImageSetVectorCIter, i, mTempSets) + { + if (*i) + (*i)->incRef(); + } + + mOutputPauseLeft = 0; + + if (mParticleImage) + mParticleImage->incRef(); + + return *this; +} + +ParticleEmitter::~ParticleEmitter() +{ + FOR_EACH (ImageSetVectorCIter, i, mTempSets) + { + if (*i) + (*i)->decRef(); + } + mTempSets.clear(); + + if (mParticleImage) + { + mParticleImage->decRef(); + mParticleImage = nullptr; + } +} + +template ParticleEmitterProp +ParticleEmitter::readParticleEmitterProp(XmlNodePtr propertyNode, T def) +{ + ParticleEmitterProp retval; + + def = static_cast(XML::getFloatProperty(propertyNode, "value", + static_cast(def))); + retval.set(static_cast(XML::getFloatProperty(propertyNode, "min", + static_cast(def))), static_cast(XML::getFloatProperty( + propertyNode, "max", static_cast(def)))); + + const std::string change = XML::getProperty( + propertyNode, "change-func", "none"); + T amplitude = static_cast(XML::getFloatProperty(propertyNode, + "change-amplitude", 0.0)); + + const int period = XML::getProperty(propertyNode, "change-period", 0); + const int phase = XML::getProperty(propertyNode, "change-phase", 0); + if (change == "saw" || change == "sawtooth") + retval.setFunction(FUNC_SAW, amplitude, period, phase); + else if (change == "sine" || change == "sinewave") + retval.setFunction(FUNC_SINE, amplitude, period, phase); + else if (change == "triangle") + retval.setFunction(FUNC_TRIANGLE, amplitude, period, phase); + else if (change == "square") + retval.setFunction(FUNC_SQUARE, amplitude, period, phase); + + return retval; +} + +std::list ParticleEmitter::createParticles(const int tick) +{ + std::list newParticles; + + if (mOutputPauseLeft > 0) + { + mOutputPauseLeft --; + return newParticles; + } + mOutputPauseLeft = mOutputPause.value(tick); + + for (int i = mOutput.value(tick); i > 0; i--) + { + // Limit maximum particles + if (Particle::particleCount > Particle::maxCount) + break; + + Particle *newParticle; + if (mParticleImage) + { + const std::string name = mParticleImage->getIdPath(); + if (ImageParticle::imageParticleCountByName.find(name) == + ImageParticle::imageParticleCountByName.end()) + { + ImageParticle::imageParticleCountByName[name] = 0; + } + + if (ImageParticle::imageParticleCountByName[name] > 200) + break; + + newParticle = new ImageParticle(mMap, mParticleImage); + } + else if (!mParticleRotation.mFrames.empty()) + { + Animation *const newAnimation = new Animation(mParticleRotation); + newParticle = new RotationalParticle(mMap, newAnimation); + } + else if (!mParticleAnimation.mFrames.empty()) + { + Animation *const newAnimation = new Animation(mParticleAnimation); + newParticle = new AnimationParticle(mMap, newAnimation); + } + else + { + newParticle = new Particle(mMap); + } + + const Vector position(mParticlePosX.value(tick), + mParticlePosY.value(tick), + mParticlePosZ.value(tick)); + newParticle->moveTo(position); + + const float angleH = mParticleAngleHorizontal.value(tick); + const float cosAngleH = cos(angleH); + const float sinAngleH = sin(angleH); + const float angleV = mParticleAngleVertical.value(tick); + const float cosAngleV = cos(angleV); + const float sinAngleV = sin(angleV); + const float power = mParticlePower.value(tick); + newParticle->setVelocity(cosAngleH * cosAngleV * power, + sinAngleH * cosAngleV * power, + sinAngleV * power); + + newParticle->setRandomness(mParticleRandomness.value(tick)); + newParticle->setGravity(mParticleGravity.value(tick)); + newParticle->setBounce(mParticleBounce.value(tick)); + newParticle->setFollow(mParticleFollow); + + newParticle->setDestination(mParticleTarget, + mParticleAcceleration.value(tick), + mParticleMomentum.value(tick)); + + newParticle->setDieDistance(mParticleDieDistance.value(tick)); + + newParticle->setLifetime(mParticleLifetime.value(tick)); + newParticle->setFadeOut(mParticleFadeOut.value(tick)); + newParticle->setFadeIn(mParticleFadeIn.value(tick)); + newParticle->setAlpha(mParticleAlpha.value(tick)); + + FOR_EACH (ParticleEmitterListCIter, it, mParticleChildEmitters) + newParticle->addEmitter(new ParticleEmitter(*it)); + + if (!mDeathEffect.empty()) + newParticle->setDeathEffect(mDeathEffect, mDeathEffectConditions); + + newParticles.push_back(newParticle); + } + + return newParticles; +} + +void ParticleEmitter::adjustSize(const int w, const int h) +{ + if (w == 0 || h == 0) + return; // new dimensions are illegal + + // calculate the old rectangle + const int oldArea = static_cast( + mParticlePosX.maxVal - mParticlePosX.minVal) * static_cast( + mParticlePosX.maxVal - mParticlePosY.minVal); + if (oldArea == 0) + { + // when the effect has no dimension it is + // not designed to be resizeable + return; + } + + // set the new dimensions + mParticlePosX.set(0, static_cast(w)); + mParticlePosY.set(0, static_cast(h)); + const int newArea = w * h; + // adjust the output so that the particle density stays the same + const float outputFactor = static_cast(newArea) + / static_cast(oldArea); + mOutput.minVal = static_cast(static_cast( + mOutput.minVal) * outputFactor); + mOutput.maxVal = static_cast(static_cast( + mOutput.maxVal) * outputFactor); +} diff --git a/src/particle/particleemitter.h b/src/particle/particleemitter.h new file mode 100644 index 000000000..415c10f37 --- /dev/null +++ b/src/particle/particleemitter.h @@ -0,0 +1,167 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_PARTICLEEMITTER_H +#define PARTICLE_PARTICLEEMITTER_H + +#include "particle/particleemitterprop.h" + +#include "resources/animation.h" + +#include "utils/xml.h" + +#include + +class Image; +class ImageSet; +class Map; +class Particle; + +/** + * Every Particle can have one or more particle emitters that create new + * particles when they are updated + */ +class ParticleEmitter final +{ + public: + ParticleEmitter(const XmlNodePtr emitterNode, Particle *const target, + Map *const map, const int rotation = 0, + const std::string& dyePalettes = std::string()); + + /** + * Copy Constructor (necessary for reference counting of particle images) + */ + ParticleEmitter(const ParticleEmitter &o); + + /** + * Assignment operator that calls the copy constructor + */ + ParticleEmitter & operator=(const ParticleEmitter &o); + + /** + * Destructor. + */ + ~ParticleEmitter(); + + /** + * Spawns new particles + * @return: a list of created particles + */ + std::list createParticles(const int tick); + + /** + * Sets the target of the particles that are created + */ + void setTarget(Particle *const target) + { mParticleTarget = target; } + + /** + * Changes the size of the emitter so that the effect fills a + * rectangle of this size + */ + void adjustSize(const int w, const int h); + + private: + template ParticleEmitterProp + readParticleEmitterProp(XmlNodePtr propertyNode, T def); + + ImageSet *getImageSet(XmlNodePtr node); + + /** + * initial position of particles: + */ + ParticleEmitterProp mParticlePosX, mParticlePosY, mParticlePosZ; + + /** + * initial vector of particles: + */ + ParticleEmitterProp mParticleAngleHorizontal, + mParticleAngleVertical; + + /** + * Initial velocity of particles + */ + ParticleEmitterProp mParticlePower; + + /* + * Vector changing of particles: + */ + ParticleEmitterProp mParticleGravity; + ParticleEmitterProp mParticleRandomness; + ParticleEmitterProp mParticleBounce; + + /* + * Properties of targeting particles: + */ + Particle *mParticleTarget; + ParticleEmitterProp mParticleAcceleration; + ParticleEmitterProp mParticleDieDistance; + ParticleEmitterProp mParticleMomentum; + + /* + * Behavior over time of the particles: + */ + ParticleEmitterProp mParticleLifetime; + ParticleEmitterProp mParticleFadeOut; + ParticleEmitterProp mParticleFadeIn; + + // Map the particles are spawned on + Map *mMap; + + // Number of particles spawned per update + ParticleEmitterProp mOutput; + + // Pause in frames between two spawns + ParticleEmitterProp mOutputPause; + + /* + * Graphical representation of the particles + */ + // Particle image, if used + Image *mParticleImage; + + // Filename of particle animation file + Animation mParticleAnimation; + + // Filename of particle rotation file + Animation mParticleRotation; + + // Opacity of the graphical representation of the particles + ParticleEmitterProp mParticleAlpha; + + /* + * Death effect of the particles + */ + std::string mDeathEffect; + + // List of emitters the spawned particles are equipped with + std::list mParticleChildEmitters; + + std::vector mTempSets; + + int mOutputPauseLeft; + + signed char mDeathEffectConditions; + + bool mParticleFollow; +}; +#endif // PARTICLE_PARTICLEEMITTER_H diff --git a/src/particle/particleemitterprop.h b/src/particle/particleemitterprop.h new file mode 100644 index 000000000..ea241e740 --- /dev/null +++ b/src/particle/particleemitterprop.h @@ -0,0 +1,132 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_PARTICLEEMITTERPROP_H +#define PARTICLE_PARTICLEEMITTERPROP_H + +#include +#include + +#include "localconsts.h" + +/** + * Returns a random numeric value that is larger than or equal min and smaller + * than max + */ + +enum ChangeFunc +{ + FUNC_NONE = 0, + FUNC_SINE, + FUNC_SAW, + FUNC_TRIANGLE, + FUNC_SQUARE +}; + +template struct ParticleEmitterProp final +{ + ParticleEmitterProp(): + minVal(0), maxVal(0), changeFunc(FUNC_NONE), + changeAmplitude(0), changePeriod(0), changePhase(0) + { + } + + void set(const T min, const T max) + { + minVal = min; + maxVal = max; + } + + void set(const T val) + { + set(val, val); + } + + void setFunction(ChangeFunc func, T amplitude, + const int period, const int phase) + { + changeFunc = func; + changeAmplitude = amplitude; + changePeriod = period; + if (!changePeriod) + changePeriod = 1; + changePhase = phase; + } + + T value(int tick) const + { + tick += changePhase; + T val = static_cast(minVal + (maxVal - minVal) + * (rand() / (static_cast(RAND_MAX) + 1))); + + switch (changeFunc) + { + case FUNC_SINE: + val += static_cast(std::sin(M_PI * 2 * (static_cast( + tick % changePeriod) / static_cast( + changePeriod)))) * changeAmplitude; + break; + case FUNC_SAW: + val += static_cast(changeAmplitude * (static_cast( + tick % changePeriod) / static_cast( + changePeriod))) * 2 - changeAmplitude; + break; + case FUNC_TRIANGLE: + if ((tick % changePeriod) * 2 < changePeriod) + { + val += changeAmplitude - static_cast(( + tick % changePeriod) / static_cast( + changePeriod)) * changeAmplitude * 4; + } + else + { + val += changeAmplitude * -3 + static_cast(( + tick % changePeriod) / static_cast( + changePeriod)) * changeAmplitude * 4; + // I have no idea why this works but it does + } + break; + case FUNC_SQUARE: + if ((tick % changePeriod) * 2 < changePeriod) + val += changeAmplitude; + else + val -= changeAmplitude; + break; + case FUNC_NONE: + default: + // nothing + break; + } + + return val; + } + + T minVal; + T maxVal; + + ChangeFunc changeFunc; + T changeAmplitude; + int changePeriod; + int changePhase; +}; + +#endif // PARTICLE_PARTICLEEMITTERPROP_H diff --git a/src/particle/rotationalparticle.cpp b/src/particle/rotationalparticle.cpp new file mode 100644 index 000000000..b72a94b3e --- /dev/null +++ b/src/particle/rotationalparticle.cpp @@ -0,0 +1,96 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/rotationalparticle.h" + +#include "simpleanimation.h" + +#include "render/graphics.h" + +#include + +#include "debug.h" + +static const double PI = M_PI; +static const float PI2 = 2 * M_PI; + +RotationalParticle::RotationalParticle(Map *const map, + Animation *const animation) : + ImageParticle(map, nullptr), + mAnimation(new SimpleAnimation(animation)) +{ +} + +RotationalParticle::RotationalParticle(Map *const map, + const XmlNodePtr animationNode, + const std::string& dyePalettes): + ImageParticle(map, nullptr), + mAnimation(new SimpleAnimation(animationNode, dyePalettes)) +{ +} + +RotationalParticle::~RotationalParticle() +{ + delete mAnimation; + mAnimation = nullptr; + mImage = nullptr; +} + +bool RotationalParticle::update() +{ + if (!mAnimation) + return false; + + // TODO: cache velocities to avoid spamming atan2() + + const int size = mAnimation->getLength(); + if (!size) + return false; + + float rad = static_cast(atan2(mVelocity.x, mVelocity.y)); + if (rad < 0) + rad = PI2 + rad; + + const float range = static_cast(PI / size); + + // Determines which frame the particle should play + if (rad < range || rad > PI2 - range) + { + mAnimation->setFrame(0); + } + else + { + for (int c = 1; c < size; c++) + { + if (((static_cast(c) * (2 * range)) - range) < rad + && rad < ((static_cast(c) * (2 * range)) + range)) + { + mAnimation->setFrame(c); + break; + } + } + } + + mImage = mAnimation->getCurrentImage(); + + return Particle::update(); +} diff --git a/src/particle/rotationalparticle.h b/src/particle/rotationalparticle.h new file mode 100644 index 000000000..33289d8c3 --- /dev/null +++ b/src/particle/rotationalparticle.h @@ -0,0 +1,52 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_ROTATIONALPARTICLE_H +#define PARTICLE_ROTATIONALPARTICLE_H + +#include "particle/imageparticle.h" + +#include "utils/xml.h" + +class Animation; +class Map; +class SimpleAnimation; + +class RotationalParticle final : public ImageParticle +{ + public: + RotationalParticle(Map *const map, Animation *const animation); + + RotationalParticle(Map *const map, const XmlNodePtr animationNode, + const std::string& dyePalettes = std::string()); + + A_DELETE_COPY(RotationalParticle) + + ~RotationalParticle(); + + virtual bool update() override; + + private: + SimpleAnimation *mAnimation; /**< Used animation for this particle */ +}; + +#endif // PARTICLE_ROTATIONALPARTICLE_H diff --git a/src/particle/textparticle.cpp b/src/particle/textparticle.cpp new file mode 100644 index 000000000..ecab98068 --- /dev/null +++ b/src/particle/textparticle.cpp @@ -0,0 +1,89 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#include "particle/textparticle.h" + +#include "render/graphics.h" + +#include "gui/theme.h" + +#include +#include + +#include "debug.h" + +TextParticle::TextParticle(Map *const map, const std::string &text, + const gcn::Color *const color, + gcn::Font *const font, const bool outline) : + Particle(map), + mText(text), + mTextFont(font), + mColor(color), + mTextWidth(mTextFont ? mTextFont->getWidth(mText) / 2 : 1), + mOutline(outline) +{ +} + +bool TextParticle::draw(Graphics *const graphics, + const int offsetX, const int offsetY) const +{ + if (!mColor || !mTextFont) + return false; + + BLOCK_START("TextParticle::draw") + if (!isAlive()) + { + BLOCK_END("TextParticle::draw") + return false; + } + + const int screenX = static_cast(mPos.x) + offsetX; + const int screenY = static_cast(mPos.y) - static_cast(mPos.z) + + offsetY; + + float alpha = mAlpha * 255.0f; + + if (mFadeOut && mLifetimeLeft > -1 && mLifetimeLeft < mFadeOut) + { + alpha *= static_cast(mLifetimeLeft) + / static_cast(mFadeOut); + } + + if (mFadeIn && mLifetimePast < mFadeIn) + { + alpha *= static_cast(mLifetimePast) + / static_cast(mFadeIn); + } + + gcn::Color color = *mColor; + color.a = static_cast(alpha); + + graphics->setColor(color); + if (mOutline) + { + graphics->setColor2(Theme::getThemeColor( + Theme::OUTLINE, static_cast(alpha))); + } + mTextFont->drawString(graphics, mText, screenX - mTextWidth, screenY); + BLOCK_END("TextParticle::draw") + return true; +} diff --git a/src/particle/textparticle.h b/src/particle/textparticle.h new file mode 100644 index 000000000..a243e32e0 --- /dev/null +++ b/src/particle/textparticle.h @@ -0,0 +1,62 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2013 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 . + */ + +#ifndef PARTICLE_TEXTPARTICLE_H +#define PARTICLE_TEXTPARTICLE_H + +#include "particle/particle.h" + +class TextParticle final : public Particle +{ + public: + /** + * Constructor. + */ + TextParticle(Map *const map, const std::string &text, + const gcn::Color *const color, + gcn::Font *const font, const bool outline = false); + + A_DELETE_COPY(TextParticle) + + /** + * Draws the particle image. + */ + virtual bool draw(Graphics *const graphics, + const int offsetX, const int offsetY) const override; + + // hack to improve text visibility + virtual int getPixelY() const override A_WARN_UNUSED + { return static_cast(mPos.y + mPos.z); } + + // hack to improve text visibility (for sorting only) + virtual int getSortPixelY() const override A_WARN_UNUSED + { return static_cast(mPos.y + mPos.z); } + + private: + std::string mText; /**< Text of the particle. */ + gcn::Font *mTextFont; /**< Font used for drawing the text. */ + const gcn::Color *mColor; /**< Color used for drawing the text. */ + int mTextWidth; + bool mOutline; /**< Make the text better readable */ +}; + +#endif // PARTICLE_TEXTPARTICLE_H -- cgit v1.2.3-70-g09d2