summaryrefslogtreecommitdiff
path: root/src/particle
diff options
context:
space:
mode:
Diffstat (limited to 'src/particle')
-rw-r--r--src/particle/particle.cpp33
-rw-r--r--src/particle/particle.h26
-rw-r--r--src/particle/particleemitter.cpp2
-rw-r--r--src/particle/particleengine.cpp558
-rw-r--r--src/particle/particleengine.h353
5 files changed, 921 insertions, 51 deletions
diff --git a/src/particle/particle.cpp b/src/particle/particle.cpp
index 4e583d0ab..8f0cb1e41 100644
--- a/src/particle/particle.cpp
+++ b/src/particle/particle.cpp
@@ -41,20 +41,11 @@
#include "debug.h"
-Particle *particleEngine = nullptr;
-
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() :
Actor(),
mAlpha(1.0F),
@@ -79,26 +70,14 @@ Particle::Particle() :
mAllowSizeAdjust(false),
mFollow(false)
{
- Particle::particleCount++;
+ ParticleEngine::particleCount++;
}
Particle::~Particle()
{
// Delete child emitters and child particles
clear();
- Particle::particleCount--;
-}
-
-void Particle::setupEngine() restrict2
-{
- 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");
+ ParticleEngine::particleCount--;
}
void Particle::draw(Graphics *restrict const,
@@ -123,7 +102,7 @@ void Particle::updateSelf() restrict2
dist.x *= SIN45;
float invHypotenuse;
- switch (Particle::fastPhysics)
+ switch (ParticleEngine::fastPhysics)
{
case 1:
invHypotenuse = fastInvSqrt(
@@ -191,14 +170,14 @@ void Particle::updateSelf() restrict2
mAlive = AliveStatus::DEAD_FLOOR;
}
}
- else if (mPos.z > PARTICLE_SKY)
+ else if (mPos.z > ParticleEngine::PARTICLE_SKY)
{
mAlive = AliveStatus::DEAD_SKY;
}
// Update child emitters
- if (Particle::emitterSkip && (mLifetimePast - 1)
- % Particle::emitterSkip == 0)
+ if (ParticleEngine::emitterSkip &&
+ (mLifetimePast - 1) % ParticleEngine::emitterSkip == 0)
{
FOR_EACH (EmitterConstIterator, e, mChildEmitters)
{
diff --git a/src/particle/particle.h b/src/particle/particle.h
index 323e1c171..01fea38e0 100644
--- a/src/particle/particle.h
+++ b/src/particle/particle.h
@@ -27,6 +27,8 @@
#include "enums/particle/alivestatus.h"
+#include "particle/particleengine.h"
+
#include "localconsts.h"
class Color;
@@ -34,27 +36,13 @@ class Font;
class Particle;
class ParticleEmitter;
-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 notfinal : public Actor
{
public:
- 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
+ friend class ParticleEngine;
Particle();
@@ -71,12 +59,6 @@ class Particle notfinal : public Actor
void clear() restrict2;
/**
- * Gives a particle the properties of an engine root particle and loads
- * the particle-related config settings.
- */
- void setupEngine() restrict2;
-
- /**
* Updates particle position, returns false when the particle should
* be deleted.
*/
@@ -351,6 +333,4 @@ class Particle notfinal : public Actor
bool mFollow;
};
-extern Particle *particleEngine;
-
#endif // PARTICLE_PARTICLE_H
diff --git a/src/particle/particleemitter.cpp b/src/particle/particleemitter.cpp
index d82282060..44486e98e 100644
--- a/src/particle/particleemitter.cpp
+++ b/src/particle/particleemitter.cpp
@@ -508,7 +508,7 @@ void ParticleEmitter::createParticles(const int tick,
for (int i = mOutput.value(tick); i > 0; i--)
{
// Limit maximum particles
- if (Particle::particleCount > Particle::maxCount)
+ if (ParticleEngine::particleCount > ParticleEngine::maxCount)
break;
Particle *newParticle = nullptr;
diff --git a/src/particle/particleengine.cpp b/src/particle/particleengine.cpp
new file mode 100644
index 000000000..1a65b51d8
--- /dev/null
+++ b/src/particle/particleengine.cpp
@@ -0,0 +1,558 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2016 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 "logger.h"
+
+#include "gui/viewport.h"
+
+#include "particle/animationparticle.h"
+#include "particle/particleemitter.h"
+#include "particle/rotationalparticle.h"
+#include "particle/textparticle.h"
+
+#include "resources/resourcemanager.h"
+
+#include "resources/dye/dye.h"
+
+#include "utils/dtor.h"
+#include "utils/mathutils.h"
+
+#include "debug.h"
+
+ParticleEngine *particleEngine = nullptr;
+
+static const float SIN45 = 0.707106781F;
+
+class Graphics;
+class Image;
+
+int ParticleEngine::particleCount = 0;
+int ParticleEngine::maxCount = 0;
+int ParticleEngine::fastPhysics = 0;
+int ParticleEngine::emitterSkip = 1;
+bool ParticleEngine::enabled = true;
+const float ParticleEngine::PARTICLE_SKY = 800.0F;
+
+ParticleEngine::ParticleEngine() :
+ Actor(),
+ mAlpha(1.0F),
+ mLifetimeLeft(-1),
+ mLifetimePast(0),
+ mFadeOut(0),
+ mFadeIn(0),
+ mVelocity(),
+ mAlive(AliveStatus::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)
+{
+ ParticleEngine::particleCount++;
+}
+
+ParticleEngine::~ParticleEngine()
+{
+ // Delete child emitters and child particles
+ clear();
+ ParticleEngine::particleCount--;
+}
+
+void ParticleEngine::setupEngine() restrict2
+{
+ ParticleEngine::maxCount = config.getIntValue("particleMaxCount");
+ ParticleEngine::fastPhysics = config.getIntValue("particleFastPhysics");
+ ParticleEngine::emitterSkip = config.getIntValue("particleEmitterSkip") + 1;
+ if (!ParticleEngine::emitterSkip)
+ ParticleEngine::emitterSkip = 1;
+ ParticleEngine::enabled = config.getBoolValue("particleeffects");
+ disableAutoDelete();
+ logger->log1("Particle engine set up");
+}
+
+void ParticleEngine::draw(Graphics *restrict const,
+ const int, const int) const restrict2
+{
+}
+
+void ParticleEngine::updateSelf() restrict2
+{
+ if (mLifetimeLeft == 0 && mAlive == AliveStatus::ALIVE)
+ mAlive = AliveStatus::DEAD_TIMEOUT;
+
+ if (mAlive == AliveStatus::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 (ParticleEngine::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 = AliveStatus::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 = AliveStatus::DEAD_FLOOR;
+ }
+ }
+ else if (mPos.z > PARTICLE_SKY)
+ {
+ mAlive = AliveStatus::DEAD_SKY;
+ }
+
+ // Update child emitters
+ if (ParticleEngine::emitterSkip && (mLifetimePast - 1)
+ % ParticleEngine::emitterSkip == 0)
+ {
+ FOR_EACH (EmitterConstIterator, e, mChildEmitters)
+ {
+ std::vector<Particle*> newParticles;
+ (*e)->createParticles(mLifetimePast, newParticles);
+ FOR_EACH (std::vector<Particle*>::const_iterator,
+ it,
+ newParticles)
+ {
+ Particle *const p = *it;
+ p->moveBy(mPos);
+ mChildParticles.push_back(p);
+ }
+ }
+ }
+ }
+
+ // create death effect when the particle died
+ if (mAlive != AliveStatus::ALIVE &&
+ mAlive != AliveStatus::DEAD_LONG_AGO)
+ {
+ if ((CAST_U32(mAlive) & mDeathEffectConditions)
+ > 0x00 && !mDeathEffect.empty())
+ {
+ Particle *restrict const deathEffect = particleEngine->addEffect(
+ mDeathEffect, 0, 0);
+ if (deathEffect)
+ deathEffect->moveBy(mPos);
+ }
+ mAlive = AliveStatus::DEAD_LONG_AGO;
+ }
+}
+
+bool ParticleEngine::update() restrict2
+{
+ if (!mMap)
+ return false;
+
+ const Vector oldPos = mPos;
+
+ updateSelf();
+
+ const Vector change = mPos - oldPos;
+
+ if (mChildParticles.empty())
+ {
+ if (mAlive != AliveStatus::ALIVE &&
+ mAutoDelete)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ // Update child particles
+
+ const int cameraX = viewport->getCameraX();
+ const int cameraY = viewport->getCameraY();
+ const float x1 = static_cast<float>(cameraX - 3000);
+ const float y1 = static_cast<float>(cameraY - 2000);
+ const float x2 = static_cast<float>(cameraX + 3000);
+ const float y2 = static_cast<float>(cameraY + 2000);
+
+ for (ParticleIterator p = mChildParticles.begin(),
+ fp2 = mChildParticles.end(); p != fp2; )
+ {
+ Particle *restrict const particle = *p;
+ const float posX = particle->mPos.x;
+ const float posY = particle->mPos.y;
+ if (posX < x1 || posX > x2 || posY < y1 || posY > y2)
+ {
+ ++p;
+ continue;
+ }
+ // 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 != AliveStatus::ALIVE &&
+ mChildParticles.empty() &&
+ mAutoDelete)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void ParticleEngine::moveBy(const Vector &restrict change) restrict2
+{
+ mPos += change;
+ FOR_EACH (ParticleConstIterator, p, mChildParticles)
+ {
+ Particle *restrict const particle = *p;
+ if (particle->mFollow)
+ particle->moveBy(change);
+ }
+}
+
+void ParticleEngine::moveTo(const float x, const float y) restrict2
+{
+ moveTo(Vector(x, y, mPos.z));
+}
+
+Particle *ParticleEngine::createChild() restrict2
+{
+ Particle *const newParticle = new Particle();
+ newParticle->setMap(mMap);
+ mChildParticles.push_back(newParticle);
+ return newParticle;
+}
+
+Particle *ParticleEngine::addEffect(const std::string &restrict
+ particleEffectFile,
+ const int pixelX,
+ const int pixelY,
+ const int rotation) restrict2
+{
+ 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),
+ UseResman_true,
+ SkipError_false);
+ const XmlNodePtrConst rootNode = doc.rootNode();
+
+ if (!rootNode || !xmlNameEqual(rootNode, "effect"))
+ {
+ logger->log("Error loading particle: %s", particleEffectFile.c_str());
+ return nullptr;
+ }
+
+ // 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(node, dyePalettes);
+ newParticle->setMap(mMap);
+ }
+ // Rotational
+ else if ((node = XML::findFirstChildByName(
+ effectChildNode, "rotation")))
+ {
+ newParticle = new RotationalParticle(node, dyePalettes);
+ newParticle->setMap(mMap);
+ }
+ // Image
+ else if ((node = XML::findFirstChildByName(effectChildNode, "image")))
+ {
+ std::string imageSrc;
+ if (XmlHaveChildContent(node))
+ imageSrc = XmlChildContent(node);
+ if (!imageSrc.empty() && !dyePalettes.empty())
+ Dye::instantiate(imageSrc, dyePalettes);
+ Image *const img = resourceManager->getImage(imageSrc);
+
+ newParticle = new ImageParticle(img);
+ newParticle->setMap(mMap);
+ }
+ // Other
+ else
+ {
+ newParticle = new Particle();
+ newParticle->setMap(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 *restrict const newEmitter =
+ new ParticleEmitter(
+ emitterNode,
+ newParticle,
+ mMap,
+ rotation,
+ dyePalettes);
+ newParticle->addEmitter(newEmitter);
+ }
+ else if (xmlNameEqual(emitterNode, "deatheffect"))
+ {
+ std::string deathEffect;
+ if (node && XmlHaveChildContent(node))
+ deathEffect = XmlChildContent(emitterNode);
+
+ char deathEffectConditions = 0x00;
+ if (XML::getBoolProperty(emitterNode, "on-floor", true))
+ {
+ deathEffectConditions += CAST_S8(
+ AliveStatus::DEAD_FLOOR);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-sky", true))
+ {
+ deathEffectConditions += CAST_S8(
+ AliveStatus::DEAD_SKY);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-other", false))
+ {
+ deathEffectConditions += CAST_S8(
+ AliveStatus::DEAD_OTHER);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-impact", true))
+ {
+ deathEffectConditions += CAST_S8(
+ AliveStatus::DEAD_IMPACT);
+ }
+ if (XML::getBoolProperty(emitterNode, "on-timeout", true))
+ {
+ deathEffectConditions += CAST_S8(
+ AliveStatus::DEAD_TIMEOUT);
+ }
+ newParticle->setDeathEffect(
+ deathEffect, deathEffectConditions);
+ }
+ }
+
+ mChildParticles.push_back(newParticle);
+ }
+
+ return newParticle;
+}
+
+Particle *ParticleEngine::addTextSplashEffect(const std::string &restrict text,
+ const int x,
+ const int y,
+ const Color *restrict const
+ color,
+ Font *restrict const font,
+ const bool outline) restrict2
+{
+ Particle *const newParticle = new TextParticle(
+ text,
+ color,
+ font,
+ outline);
+ newParticle->setMap(mMap);
+ 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 *ParticleEngine::addTextRiseFadeOutEffect(const std::string &restrict
+ text,
+ const int x,
+ const int y,
+ const Color *restrict const
+ color,
+ Font *restrict const font,
+ const bool outline) restrict2
+{
+ Particle *const newParticle = new TextParticle(
+ text,
+ color,
+ font,
+ outline);
+ newParticle->setMap(mMap);
+ 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 ParticleEngine::adjustEmitterSize(const int w, const int h) restrict2
+{
+ if (mAllowSizeAdjust)
+ {
+ FOR_EACH (EmitterConstIterator, e, mChildEmitters)
+ (*e)->adjustSize(w, h);
+ }
+}
+
+void ParticleEngine::prepareToDie() restrict2
+{
+ FOR_EACH (ParticleIterator, p, mChildParticles)
+ {
+ Particle *restrict const particle = *p;
+ if (!particle)
+ continue;
+ particle->prepareToDie();
+ if (particle->isAlive() &&
+ particle->mLifetimeLeft == -1 &&
+ particle->mAutoDelete)
+ {
+ particle->kill();
+ }
+ }
+}
+
+void ParticleEngine::clear() restrict2
+{
+ delete_all(mChildEmitters);
+ mChildEmitters.clear();
+
+ delete_all(mChildParticles);
+ mChildParticles.clear();
+}
diff --git a/src/particle/particleengine.h b/src/particle/particleengine.h
new file mode 100644
index 000000000..8916c0ac1
--- /dev/null
+++ b/src/particle/particleengine.h
@@ -0,0 +1,353 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2006-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2016 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_PARTICLEENGINE_H
+#define PARTICLE_PARTICLEENGINE_H
+
+#include "being/actor.h"
+
+#include "enums/particle/alivestatus.h"
+
+#include "localconsts.h"
+
+class Color;
+class Font;
+class Particle;
+class ParticleEmitter;
+
+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;
+
+class ParticleEngine final : public Actor
+{
+ public:
+ 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
+
+ ParticleEngine();
+
+ A_DELETE_COPY(ParticleEngine)
+
+ /**
+ * Destructor.
+ */
+ ~ParticleEngine();
+
+ /**
+ * Deletes all child particles and emitters.
+ */
+ void clear() restrict2;
+
+ /**
+ * Gives a particle the properties of an engine root particle and loads
+ * the particle-related config settings.
+ */
+ void setupEngine() restrict2;
+
+ /**
+ * Updates particle position, returns false when the particle should
+ * be deleted.
+ */
+ bool update() restrict2;
+
+ /**
+ * Draws the particle image.
+ */
+ void draw(Graphics *restrict const graphics,
+ const int offsetX,
+ const int offsetY) const restrict2 override
+ A_CONST A_NONNULL(2);
+
+ /**
+ * Necessary for sorting with the other sprites.
+ */
+ int getPixelY() const restrict2 override A_WARN_UNUSED
+ { return CAST_S32(mPos.y) - 16; }
+
+ /**
+ * Necessary for sorting with the other sprites for sorting only.
+ */
+ int getSortPixelY() const restrict2 override A_WARN_UNUSED
+ { return CAST_S32(mPos.y) - 16; }
+
+ /**
+ * Creates a blank particle as a child of the current particle
+ * Useful for creating target particles
+ */
+ Particle *createChild() restrict2;
+
+ /**
+ * Creates a child particle that hosts some emitters described in the
+ * particleEffectFile.
+ */
+ Particle *addEffect(const std::string &restrict particleEffectFile,
+ const int pixelX, const int pixelY,
+ const int rotation = 0) restrict2;
+
+ /**
+ * Creates a standalone text particle.
+ */
+ Particle *addTextSplashEffect(const std::string &restrict text,
+ const int x, const int y,
+ const Color *restrict const color,
+ Font *restrict const font,
+ const bool outline = false)
+ restrict2 A_NONNULL(5, 6);
+
+ /**
+ * Creates a standalone text particle.
+ */
+ Particle *addTextRiseFadeOutEffect(const std::string &restrict text,
+ const int x, const int y,
+ const Color *restrict const color,
+ Font *restrict const font,
+ const bool outline = false)
+ restrict2 A_NONNULL(5, 6);
+
+ /**
+ * Adds an emitter to the particle.
+ */
+ void addEmitter(ParticleEmitter *const emitter) restrict2 A_NONNULL(2)
+ { mChildEmitters.push_back(emitter); }
+
+ /**
+ * Sets the position in 3 dimensional space in pixels relative to map.
+ */
+ void moveTo(const Vector &restrict pos) restrict2
+ { moveBy(pos - mPos); }
+
+ /**
+ * Sets the position in 2 dimensional space in pixels relative to map.
+ */
+ void moveTo(const float x, const float y) restrict2;
+
+ /**
+ * Changes the particle position relative
+ */
+ void moveBy(const Vector &restrict change) restrict2;
+
+ /**
+ * Sets the time in game ticks until the particle is destroyed.
+ */
+ void setLifetime(const int lifetime) restrict2 noexcept
+ { 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) restrict2 noexcept
+ { mFadeOut = fadeOut; }
+
+ /**
+ * Sets the remaining particle lifetime where the particle starts to
+ * fade out.
+ */
+ void setFadeIn(const int fadeIn) restrict2 noexcept
+ { mFadeIn = fadeIn; }
+
+ /**
+ * Sets the current velocity in 3 dimensional space.
+ */
+ void setVelocity(const float x,
+ const float y,
+ const float z) restrict2 noexcept
+ { mVelocity.x = x; mVelocity.y = y; mVelocity.z = z; }
+
+ /**
+ * Sets the downward acceleration.
+ */
+ void setGravity(const float gravity) restrict2 noexcept
+ { mGravity = gravity; }
+
+ /**
+ * Sets the ammount of random vector changes
+ */
+ void setRandomness(const int r) restrict2 noexcept
+ { mRandomness = r; }
+
+ /**
+ * Sets the ammount of velocity particles retain after
+ * hitting the ground.
+ */
+ void setBounce(const float bouncieness) restrict2 noexcept
+ { mBounce = bouncieness; }
+
+ /**
+ * Sets the flag if the particle is supposed to be moved by its parent
+ */
+ void setFollow(const bool follow) restrict2 noexcept
+ { mFollow = follow; }
+
+ /**
+ * Gets the flag if the particle is supposed to be moved by its parent
+ */
+ bool doesFollow() const restrict2 noexcept A_WARN_UNUSED
+ { return mFollow; }
+
+ /**
+ * Makes the particle move toward another particle with a
+ * given acceleration and momentum
+ */
+ void setDestination(Particle *restrict const target,
+ const float accel,
+ const float moment) restrict2 noexcept A_NONNULL(2)
+ { 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) restrict2
+ { 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) restrict2;
+
+ void setAllowSizeAdjust(const bool adjust) restrict2 noexcept
+ { mAllowSizeAdjust = adjust; }
+
+ bool isAlive() const restrict2 noexcept A_WARN_UNUSED
+ { return mAlive == AliveStatus::ALIVE; }
+
+ void prepareToDie() restrict2;
+
+ /**
+ * Determines whether the particle and its children are all dead
+ */
+ bool isExtinct() const restrict2 noexcept A_WARN_UNUSED
+ { return !isAlive() && mChildParticles.empty(); }
+
+ /**
+ * Manually marks the particle for deletion.
+ */
+ void kill() restrict2 noexcept
+ { mAlive = AliveStatus::DEAD_OTHER; mAutoDelete = true; }
+
+ /**
+ * After calling this function the particle will only request
+ * deletion when kill() is called
+ */
+ void disableAutoDelete() restrict2 noexcept
+ { mAutoDelete = false; }
+
+ /** We consider particles (at least for now) to be one layer-sprites */
+ int getNumberOfLayers() const restrict2 override final
+ { return 1; }
+
+ float getAlpha() const restrict2 override final
+ { return 1.0F; }
+
+ void setAlpha(const float alpha A_UNUSED) restrict2 override
+ { }
+
+ void setDeathEffect(const std::string &restrict effectFile,
+ const signed char conditions) restrict2
+ { mDeathEffect = effectFile; mDeathEffectConditions = conditions; }
+
+ protected:
+ void updateSelf() restrict2;
+
+ // 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?
+ AliveStatusT 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 *restrict 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 ParticleEngine *particleEngine;
+
+#endif // PARTICLE_PARTICLEENGINE_H