summaryrefslogtreecommitdiff
path: root/src/particle
diff options
context:
space:
mode:
authorAndrei Karas <akaras@inbox.ru>2013-08-31 21:54:52 +0300
committerAndrei Karas <akaras@inbox.ru>2013-08-31 21:54:52 +0300
commit5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829 (patch)
tree8c5be5f1f93551b676660e76b4d639ea2501e972 /src/particle
parent8a999b66fd697404c6640778a6dd1ce0e747334a (diff)
downloadmv-5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829.tar.gz
mv-5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829.tar.bz2
mv-5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829.tar.xz
mv-5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829.zip
move particles into particle dir.
Diffstat (limited to 'src/particle')
-rw-r--r--src/particle/animationparticle.cpp61
-rw-r--r--src/particle/animationparticle.h52
-rw-r--r--src/particle/imageparticle.cpp110
-rw-r--r--src/particle/imageparticle.h68
-rw-r--r--src/particle/particle.cpp478
-rw-r--r--src/particle/particle.h366
-rw-r--r--src/particle/particlecontainer.cpp196
-rw-r--r--src/particle/particlecontainer.h138
-rw-r--r--src/particle/particleemitter.cpp674
-rw-r--r--src/particle/particleemitter.h167
-rw-r--r--src/particle/particleemitterprop.h132
-rw-r--r--src/particle/rotationalparticle.cpp96
-rw-r--r--src/particle/rotationalparticle.h52
-rw-r--r--src/particle/textparticle.cpp89
-rw-r--r--src/particle/textparticle.h62
15 files changed, 2741 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "particle/imageparticle.h"
+
+#include "logger.h"
+
+#include "render/graphics.h"
+
+#include "resources/image.h"
+
+#include "debug.h"
+
+std::map<std::string, int> 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<std::string, int>::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<std::string, int>::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<int>(mPos.x)
+ + offsetX - mImage->mBounds.w / 2;
+ const int screenY = static_cast<int>(mPos.y) - static_cast<int>(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<float>(mLifetimeLeft)
+ / static_cast<float>(mFadeOut);
+ }
+
+ if (mFadeIn && mLifetimePast < mFadeIn)
+ {
+ alphafactor *= static_cast<float>(mLifetimePast)
+ / static_cast<float>(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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PARTICLE_IMAGEPARTICLE_H
+#define PARTICLE_IMAGEPARTICLE_H
+
+#include "particle/particle.h"
+
+#include <map>
+
+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<std::string, int> 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <guichan/color.hpp>
+
+#include <algorithm>
+#include <cmath>
+
+#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<float>(fabs(dist.x))
+ + static_cast<float>(fabs(dist.y))
+ + static_cast<float>(fabs(dist.z)));
+ break;
+ default:
+ invHypotenuse = 1.0f / static_cast<float>(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<float>((rand() % mRandomness - rand()
+ % mRandomness)) / 1000.0f;
+ mVelocity.y += static_cast<float>((rand() % mRandomness - rand()
+ % mRandomness)) / 1000.0f;
+ mVelocity.z += static_cast<float>((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<const char*>(
+ 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<float>(XML::getFloatProperty(
+ effectChildNode, "position-x", 0));
+ const float offsetY = static_cast<float>(XML::getFloatProperty(
+ effectChildNode, "position-y", 0));
+ const float offsetZ = static_cast<float>(XML::getFloatProperty(
+ effectChildNode, "position-z", 0));
+ const Vector position(mPos.x + static_cast<float>(pixelX) + offsetX,
+ mPos.y + static_cast<float>(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<const char*>(
+ emitterNode->xmlChildrenNode->content);
+
+ char deathEffectConditions = 0x00;
+ if (XML::getBoolProperty(emitterNode, "on-floor", true))
+ {
+ deathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_FLOOR);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-sky", true))
+ {
+ deathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_SKY);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-other", false))
+ {
+ deathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_OTHER);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-impact", true))
+ {
+ deathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_IMPACT);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-timeout", true))
+ {
+ deathEffectConditions += static_cast<signed char>(
+ 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<float>(x), static_cast<float>(y));
+ newParticle->setVelocity(
+ static_cast<float>((rand() % 100) - 50) / 200.0f, // X
+ static_cast<float>((rand() % 100) - 50) / 200.0f, // Y
+ (static_cast<float>((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<float>(x), static_cast<float>(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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PARTICLE_PARTICLE_H
+#define PARTICLE_PARTICLE_H
+
+#include "actor.h"
+#include "localconsts.h"
+
+#include <list>
+#include <string>
+
+class Map;
+class Particle;
+class ParticleEmitter;
+class SDLFont;
+
+namespace gcn
+{
+ class Color;
+ class Font;
+}
+
+typedef std::list<Particle *> Particles;
+typedef Particles::iterator ParticleIterator;
+typedef Particles::const_iterator ParticleConstIterator;
+typedef std::list<ParticleEmitter *> 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<int>(mPos.y) - 16; }
+
+ /**
+ * Necessary for sorting with the other sprites for sorting only.
+ */
+ virtual int getSortPixelY() const override A_WARN_UNUSED
+ { return static_cast<int>(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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "particle/particle.h"
+#include "particle/particlecontainer.h"
+
+#include "debug.h"
+
+typedef std::list<Particle *>::iterator ParticleListIter;
+typedef std::list<Particle *>::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<Particle *>::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<Particle *>::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<unsigned>(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<unsigned>(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<Particle *>::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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PARTICLE_PARTICLECONTAINER_H
+#define PARTICLE_PARTICLECONTAINER_H
+
+#include <list>
+#include <vector>
+
+#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<Particle *> 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<Particle *> 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <cmath>
+
+#include "debug.h"
+
+static const float SIN45 = 0.707106781f;
+static const float DEG_RAD_FACTOR = 0.017453293f;
+
+typedef std::vector<ImageSet*>::const_iterator ImageSetVectorCIter;
+typedef std::list<ParticleEmitter>::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<float>(rotation);
+ mParticleAngleHorizontal.minVal *= DEG_RAD_FACTOR;
+ mParticleAngleHorizontal.maxVal
+ += static_cast<float>(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<const char*>(
+ propertyNode->xmlChildrenNode->content);
+ mDeathEffectConditions = 0x00;
+ if (XML::getBoolProperty(propertyNode, "on-floor", true))
+ {
+ mDeathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_FLOOR);
+ }
+ if (XML::getBoolProperty(propertyNode, "on-sky", true))
+ {
+ mDeathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_SKY);
+ }
+ if (XML::getBoolProperty(propertyNode, "on-other", false))
+ {
+ mDeathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_OTHER);
+ }
+ if (XML::getBoolProperty(propertyNode, "on-impact", true))
+ {
+ mDeathEffectConditions += static_cast<signed char>(
+ Particle::DEAD_IMPACT);
+ }
+ if (XML::getBoolProperty(propertyNode, "on-timeout", true))
+ {
+ mDeathEffectConditions += static_cast<signed char>(
+ 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 <typename T> ParticleEmitterProp<T>
+ParticleEmitter::readParticleEmitterProp(XmlNodePtr propertyNode, T def)
+{
+ ParticleEmitterProp<T> retval;
+
+ def = static_cast<T>(XML::getFloatProperty(propertyNode, "value",
+ static_cast<double>(def)));
+ retval.set(static_cast<T>(XML::getFloatProperty(propertyNode, "min",
+ static_cast<double>(def))), static_cast<T>(XML::getFloatProperty(
+ propertyNode, "max", static_cast<double>(def))));
+
+ const std::string change = XML::getProperty(
+ propertyNode, "change-func", "none");
+ T amplitude = static_cast<T>(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<Particle *> ParticleEmitter::createParticles(const int tick)
+{
+ std::list<Particle *> 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<int>(
+ mParticlePosX.maxVal - mParticlePosX.minVal) * static_cast<int>(
+ 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<float>(w));
+ mParticlePosY.set(0, static_cast<float>(h));
+ const int newArea = w * h;
+ // adjust the output so that the particle density stays the same
+ const float outputFactor = static_cast<float>(newArea)
+ / static_cast<float>(oldArea);
+ mOutput.minVal = static_cast<int>(static_cast<float>(
+ mOutput.minVal) * outputFactor);
+ mOutput.maxVal = static_cast<int>(static_cast<float>(
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PARTICLE_PARTICLEEMITTER_H
+#define PARTICLE_PARTICLEEMITTER_H
+
+#include "particle/particleemitterprop.h"
+
+#include "resources/animation.h"
+
+#include "utils/xml.h"
+
+#include <list>
+
+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<Particle *> 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 <typename T> ParticleEmitterProp<T>
+ readParticleEmitterProp(XmlNodePtr propertyNode, T def);
+
+ ImageSet *getImageSet(XmlNodePtr node);
+
+ /**
+ * initial position of particles:
+ */
+ ParticleEmitterProp<float> mParticlePosX, mParticlePosY, mParticlePosZ;
+
+ /**
+ * initial vector of particles:
+ */
+ ParticleEmitterProp<float> mParticleAngleHorizontal,
+ mParticleAngleVertical;
+
+ /**
+ * Initial velocity of particles
+ */
+ ParticleEmitterProp<float> mParticlePower;
+
+ /*
+ * Vector changing of particles:
+ */
+ ParticleEmitterProp<float> mParticleGravity;
+ ParticleEmitterProp<int> mParticleRandomness;
+ ParticleEmitterProp<float> mParticleBounce;
+
+ /*
+ * Properties of targeting particles:
+ */
+ Particle *mParticleTarget;
+ ParticleEmitterProp<float> mParticleAcceleration;
+ ParticleEmitterProp<float> mParticleDieDistance;
+ ParticleEmitterProp<float> mParticleMomentum;
+
+ /*
+ * Behavior over time of the particles:
+ */
+ ParticleEmitterProp<int> mParticleLifetime;
+ ParticleEmitterProp<int> mParticleFadeOut;
+ ParticleEmitterProp<int> mParticleFadeIn;
+
+ // Map the particles are spawned on
+ Map *mMap;
+
+ // Number of particles spawned per update
+ ParticleEmitterProp<int> mOutput;
+
+ // Pause in frames between two spawns
+ ParticleEmitterProp<int> 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<float> mParticleAlpha;
+
+ /*
+ * Death effect of the particles
+ */
+ std::string mDeathEffect;
+
+ // List of emitters the spawned particles are equipped with
+ std::list<ParticleEmitter> mParticleChildEmitters;
+
+ std::vector<ImageSet*> 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PARTICLE_PARTICLEEMITTERPROP_H
+#define PARTICLE_PARTICLEEMITTERPROP_H
+
+#include <cmath>
+#include <cstdlib>
+
+#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 <typename T> 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<T>(minVal + (maxVal - minVal)
+ * (rand() / (static_cast<double>(RAND_MAX) + 1)));
+
+ switch (changeFunc)
+ {
+ case FUNC_SINE:
+ val += static_cast<T>(std::sin(M_PI * 2 * (static_cast<double>(
+ tick % changePeriod) / static_cast<double>(
+ changePeriod)))) * changeAmplitude;
+ break;
+ case FUNC_SAW:
+ val += static_cast<T>(changeAmplitude * (static_cast<double>(
+ tick % changePeriod) / static_cast<double>(
+ changePeriod))) * 2 - changeAmplitude;
+ break;
+ case FUNC_TRIANGLE:
+ if ((tick % changePeriod) * 2 < changePeriod)
+ {
+ val += changeAmplitude - static_cast<T>((
+ tick % changePeriod) / static_cast<double>(
+ changePeriod)) * changeAmplitude * 4;
+ }
+ else
+ {
+ val += changeAmplitude * -3 + static_cast<T>((
+ tick % changePeriod) / static_cast<double>(
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "particle/rotationalparticle.h"
+
+#include "simpleanimation.h"
+
+#include "render/graphics.h"
+
+#include <math.h>
+
+#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<float>(atan2(mVelocity.x, mVelocity.y));
+ if (rad < 0)
+ rad = PI2 + rad;
+
+ const float range = static_cast<const float>(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<float>(c) * (2 * range)) - range) < rad
+ && rad < ((static_cast<float>(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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "particle/textparticle.h"
+
+#include "render/graphics.h"
+
+#include "gui/theme.h"
+
+#include <guichan/color.hpp>
+#include <guichan/font.hpp>
+
+#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<int>(mPos.x) + offsetX;
+ const int screenY = static_cast<int>(mPos.y) - static_cast<int>(mPos.z)
+ + offsetY;
+
+ float alpha = mAlpha * 255.0f;
+
+ if (mFadeOut && mLifetimeLeft > -1 && mLifetimeLeft < mFadeOut)
+ {
+ alpha *= static_cast<float>(mLifetimeLeft)
+ / static_cast<float>(mFadeOut);
+ }
+
+ if (mFadeIn && mLifetimePast < mFadeIn)
+ {
+ alpha *= static_cast<float>(mLifetimePast)
+ / static_cast<float>(mFadeIn);
+ }
+
+ gcn::Color color = *mColor;
+ color.a = static_cast<int>(alpha);
+
+ graphics->setColor(color);
+ if (mOutline)
+ {
+ graphics->setColor2(Theme::getThemeColor(
+ Theme::OUTLINE, static_cast<int>(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 <http://www.gnu.org/licenses/>.
+ */
+
+#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<int>(mPos.y + mPos.z); }
+
+ // hack to improve text visibility (for sorting only)
+ virtual int getSortPixelY() const override A_WARN_UNUSED
+ { return static_cast<int>(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