From 5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829 Mon Sep 17 00:00:00 2001
From: Andrei Karas <akaras@inbox.ru>
Date: Sat, 31 Aug 2013 21:54:52 +0300
Subject: move particles into particle dir.

---
 src/CMakeLists.txt                  |  30 +-
 src/Makefile.am                     |  30 +-
 src/actorsprite.h                   |   3 +-
 src/animationparticle.cpp           |  61 ----
 src/animationparticle.h             |  52 ---
 src/being.cpp                       |   3 +-
 src/effectmanager.cpp               |   3 +-
 src/game.cpp                        |   3 +-
 src/gui/debugwindow.cpp             |   3 +-
 src/imageparticle.cpp               | 110 ------
 src/imageparticle.h                 |  68 ----
 src/localplayer.cpp                 |   3 +-
 src/map.cpp                         |   3 +-
 src/net/ea/beinghandler.cpp         |   3 +-
 src/particle.cpp                    | 477 -------------------------
 src/particle.h                      | 366 --------------------
 src/particle/animationparticle.cpp  |  61 ++++
 src/particle/animationparticle.h    |  52 +++
 src/particle/imageparticle.cpp      | 110 ++++++
 src/particle/imageparticle.h        |  68 ++++
 src/particle/particle.cpp           | 478 +++++++++++++++++++++++++
 src/particle/particle.h             | 366 ++++++++++++++++++++
 src/particle/particlecontainer.cpp  | 196 +++++++++++
 src/particle/particlecontainer.h    | 138 ++++++++
 src/particle/particleemitter.cpp    | 674 ++++++++++++++++++++++++++++++++++++
 src/particle/particleemitter.h      | 167 +++++++++
 src/particle/particleemitterprop.h  | 132 +++++++
 src/particle/rotationalparticle.cpp |  96 +++++
 src/particle/rotationalparticle.h   |  52 +++
 src/particle/textparticle.cpp       |  89 +++++
 src/particle/textparticle.h         |  62 ++++
 src/particlecontainer.cpp           | 196 -----------
 src/particlecontainer.h             | 138 --------
 src/particleemitter.cpp             | 673 -----------------------------------
 src/particleemitter.h               | 167 ---------
 src/particleemitterprop.h           | 132 -------
 src/rotationalparticle.cpp          |  96 -----
 src/rotationalparticle.h            |  52 ---
 src/statuseffect.h                  |   3 +-
 src/textparticle.cpp                |  89 -----
 src/textparticle.h                  |  62 ----
 41 files changed, 2789 insertions(+), 2778 deletions(-)
 delete mode 100644 src/animationparticle.cpp
 delete mode 100644 src/animationparticle.h
 delete mode 100644 src/imageparticle.cpp
 delete mode 100644 src/imageparticle.h
 delete mode 100644 src/particle.cpp
 delete mode 100644 src/particle.h
 create mode 100644 src/particle/animationparticle.cpp
 create mode 100644 src/particle/animationparticle.h
 create mode 100644 src/particle/imageparticle.cpp
 create mode 100644 src/particle/imageparticle.h
 create mode 100644 src/particle/particle.cpp
 create mode 100644 src/particle/particle.h
 create mode 100644 src/particle/particlecontainer.cpp
 create mode 100644 src/particle/particlecontainer.h
 create mode 100644 src/particle/particleemitter.cpp
 create mode 100644 src/particle/particleemitter.h
 create mode 100644 src/particle/particleemitterprop.h
 create mode 100644 src/particle/rotationalparticle.cpp
 create mode 100644 src/particle/rotationalparticle.h
 create mode 100644 src/particle/textparticle.cpp
 create mode 100644 src/particle/textparticle.h
 delete mode 100644 src/particlecontainer.cpp
 delete mode 100644 src/particlecontainer.h
 delete mode 100644 src/particleemitter.cpp
 delete mode 100644 src/particleemitter.h
 delete mode 100644 src/particleemitterprop.h
 delete mode 100644 src/rotationalparticle.cpp
 delete mode 100644 src/rotationalparticle.h
 delete mode 100644 src/textparticle.cpp
 delete mode 100644 src/textparticle.h

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b2e943111..813e23672 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -569,8 +569,8 @@ SET(SRCS
     animatedsprite.h
     animationdelayload.cpp
     animationdelayload.h
-    animationparticle.cpp
-    animationparticle.h
+    particle/animationparticle.cpp
+    particle/animationparticle.h
     auctionmanager.cpp
     auctionmanager.h
     avatar.cpp
@@ -622,8 +622,8 @@ SET(SRCS
     guild.h
     guildmanager.cpp
     guildmanager.h
-    imageparticle.cpp
-    imageparticle.h
+    particle/imageparticle.cpp
+    particle/imageparticle.h
     imagesprite.cpp
     imagesprite.h
     inventory.cpp
@@ -680,13 +680,13 @@ SET(SRCS
     notifymanager.h
     render/nullopenglgraphics.cpp
     render/nullopenglgraphics.h
-    particle.cpp
-    particle.h
-    particlecontainer.cpp
-    particlecontainer.h
-    particleemitter.cpp
-    particleemitter.h
-    particleemitterprop.h
+    particle/particle.cpp
+    particle/particle.h
+    particle/particlecontainer.cpp
+    particle/particlecontainer.h
+    particle/particleemitter.cpp
+    particle/particleemitter.h
+    particle/particleemitterprop.h
     party.cpp
     party.h
     playerinfo.cpp
@@ -696,8 +696,8 @@ SET(SRCS
     position.cpp
     position.h
     properties.h
-    rotationalparticle.cpp
-    rotationalparticle.h
+    particle/rotationalparticle.cpp
+    particle/rotationalparticle.h
     render/safeopenglgraphics.cpp
     render/safeopenglgraphics.h
     render/sdl2graphics.cpp
@@ -721,8 +721,8 @@ SET(SRCS
     text.h
     textmanager.cpp
     textmanager.h
-    textparticle.cpp
-    textparticle.h
+    particle/textparticle.cpp
+    particle/textparticle.h
     tileset.h
     touchactions.cpp
     touchactions.h
diff --git a/src/Makefile.am b/src/Makefile.am
index d4c6779f4..252aa05f7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -580,8 +580,8 @@ manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \
 	      animatedsprite.h \
 	      animationdelayload.cpp \
 	      animationdelayload.h \
-	      animationparticle.cpp \
-	      animationparticle.h \
+	      particle/animationparticle.cpp \
+	      particle/animationparticle.h \
 	      auctionmanager.cpp \
 	      auctionmanager.h \
 	      avatar.cpp \
@@ -633,8 +633,8 @@ manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \
 	      guild.h \
 	      guildmanager.cpp \
 	      guildmanager.h \
-	      imageparticle.cpp \
-	      imageparticle.h \
+	      particle/imageparticle.cpp \
+	      particle/imageparticle.h \
 	      imagesprite.cpp \
 	      imagesprite.h \
 	      inventory.cpp \
@@ -689,13 +689,13 @@ manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \
 	      notifymanager.h \
 	      render/nullopenglgraphics.cpp \
 	      render/nullopenglgraphics.h \
-	      particle.cpp \
-	      particle.h \
-	      particlecontainer.cpp \
-	      particlecontainer.h \
-	      particleemitter.cpp \
-	      particleemitter.h \
-	      particleemitterprop.h \
+	      particle/particle.cpp \
+	      particle/particle.h \
+	      particle/particlecontainer.cpp \
+	      particle/particlecontainer.h \
+	      particle/particleemitter.cpp \
+	      particle/particleemitter.h \
+	      particle/particleemitterprop.h \
 	      party.cpp \
 	      party.h \
 	      playerinfo.cpp \
@@ -705,8 +705,8 @@ manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \
 	      position.cpp \
 	      position.h \
 	      properties.h \
-	      rotationalparticle.cpp \
-	      rotationalparticle.h \
+	      particle/rotationalparticle.cpp \
+	      particle/rotationalparticle.h \
 	      render/safeopenglgraphics.cpp\
 	      render/safeopenglgraphics.h \
 	      render/sdl2graphics.cpp \
@@ -730,8 +730,8 @@ manaplus_SOURCES += gui/widgets/avatarlistbox.cpp \
 	      text.h \
 	      textmanager.cpp \
 	      textmanager.h \
-	      textparticle.cpp \
-	      textparticle.h \
+	      particle/textparticle.cpp \
+	      particle/textparticle.h \
 	      tileset.h \
 	      touchactions.cpp \
 	      touchactions.h \
diff --git a/src/actorsprite.h b/src/actorsprite.h
index 4b2b07c52..c8b1e03df 100644
--- a/src/actorsprite.h
+++ b/src/actorsprite.h
@@ -26,7 +26,8 @@
 #include "compoundsprite.h"
 #include "localconsts.h"
 #include "map.h"
-#include "particlecontainer.h"
+
+#include "particle/particlecontainer.h"
 
 #include <SDL_types.h>
 
diff --git a/src/animationparticle.cpp b/src/animationparticle.cpp
deleted file mode 100644
index 2c4936518..000000000
--- a/src/animationparticle.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- *  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 "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/animationparticle.h b/src/animationparticle.h
deleted file mode 100644
index 02c383fea..000000000
--- a/src/animationparticle.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- *  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 ANIMATIONPARTICLE_H
-#define ANIMATIONPARTICLE_H
-
-#include "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  // ANIMATIONPARTICLE_H
diff --git a/src/being.cpp b/src/being.cpp
index 8fc8003ff..535c0c0e3 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -29,13 +29,14 @@
 #include "client.h"
 #include "effectmanager.h"
 #include "guild.h"
-#include "particle.h"
 #include "party.h"
 #include "playerrelations.h"
 #include "soundconsts.h"
 #include "soundmanager.h"
 #include "text.h"
 
+#include "particle/particle.h"
+
 #include "gui/equipmentwindow.h"
 #include "gui/socialwindow.h"
 #include "gui/speechbubble.h"
diff --git a/src/effectmanager.cpp b/src/effectmanager.cpp
index f76f3ebd0..9d41ac297 100644
--- a/src/effectmanager.cpp
+++ b/src/effectmanager.cpp
@@ -25,9 +25,10 @@
 #include "being.h"
 #include "configuration.h"
 #include "logger.h"
-#include "particle.h"
 #include "soundmanager.h"
 
+#include "particle/particle.h"
+
 #include "debug.h"
 
 EffectManager::EffectManager() :
diff --git a/src/game.cpp b/src/game.cpp
index 258ce6e60..a900eb6ed 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -34,12 +34,13 @@
 #include "guildmanager.h"
 #include "itemshortcut.h"
 #include "localplayer.h"
-#include "particle.h"
 #include "playerinfo.h"
 #include "soundmanager.h"
 #include "spellshortcut.h"
 #include "touchmanager.h"
 
+#include "particle/particle.h"
+
 #include "input/inputmanager.h"
 #include "input/joystick.h"
 #include "input/keyboardconfig.h"
diff --git a/src/gui/debugwindow.cpp b/src/gui/debugwindow.cpp
index b154a59bc..2cb633589 100644
--- a/src/gui/debugwindow.cpp
+++ b/src/gui/debugwindow.cpp
@@ -26,7 +26,8 @@
 #include "game.h"
 #include "localplayer.h"
 #include "main.h"
-#include "particle.h"
+
+#include "particle/particle.h"
 
 #include "gui/setup.h"
 #include "gui/setup_video.h"
diff --git a/src/imageparticle.cpp b/src/imageparticle.cpp
deleted file mode 100644
index 52d66178c..000000000
--- a/src/imageparticle.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- *  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 "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/imageparticle.h b/src/imageparticle.h
deleted file mode 100644
index 160567930..000000000
--- a/src/imageparticle.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- *  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 IMAGEPARTICLE_H
-#define IMAGEPARTICLE_H
-
-#include "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  // IMAGEPARTICLE_H
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
index 409d9de4f..02c1d6ed9 100644
--- a/src/localplayer.cpp
+++ b/src/localplayer.cpp
@@ -31,7 +31,6 @@
 #include "item.h"
 #include "maplayer.h"
 #include "party.h"
-#include "particle.h"
 #include "playerinfo.h"
 #include "playerrelations.h"
 #include "simpleanimation.h"
@@ -40,6 +39,8 @@
 #include "statuseffect.h"
 #include "walklayer.h"
 
+#include "particle/particle.h"
+
 #include "input/keyboardconfig.h"
 
 #include "gui/chatwindow.h"
diff --git a/src/map.cpp b/src/map.cpp
index f7082383b..30508e07a 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -27,11 +27,12 @@
 #include "localplayer.h"
 #include "maplayer.h"
 #include "notifymanager.h"
-#include "particle.h"
 #include "simpleanimation.h"
 #include "tileset.h"
 #include "walklayer.h"
 
+#include "particle/particle.h"
+
 #include "resources/ambientlayer.h"
 #include "resources/image.h"
 #include "resources/resourcemanager.h"
diff --git a/src/net/ea/beinghandler.cpp b/src/net/ea/beinghandler.cpp
index 83576d9a2..3dee60dfa 100644
--- a/src/net/ea/beinghandler.cpp
+++ b/src/net/ea/beinghandler.cpp
@@ -31,11 +31,12 @@
 #include "guild.h"
 #include "guildmanager.h"
 #include "localplayer.h"
-#include "particle.h"
 #include "party.h"
 #include "playerrelations.h"
 #include "configuration.h"
 
+#include "particle/particle.h"
+
 #include "input/inputmanager.h"
 #include "input/keyboardconfig.h"
 
diff --git a/src/particle.cpp b/src/particle.cpp
deleted file mode 100644
index dcd9422a1..000000000
--- a/src/particle.cpp
+++ /dev/null
@@ -1,477 +0,0 @@
-/*
- *  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.h"
-
-#include "animationparticle.h"
-#include "configuration.h"
-#include "resources/dye.h"
-#include "logger.h"
-#include "map.h"
-#include "particleemitter.h"
-#include "rotationalparticle.h"
-#include "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.h b/src/particle.h
deleted file mode 100644
index 61e231d20..000000000
--- a/src/particle.h
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- *  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_H
-#define 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_H
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
diff --git a/src/particlecontainer.cpp b/src/particlecontainer.cpp
deleted file mode 100644
index a890f0d9c..000000000
--- a/src/particlecontainer.cpp
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- *  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.h"
-#include "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/particlecontainer.h b/src/particlecontainer.h
deleted file mode 100644
index c78c0dbb9..000000000
--- a/src/particlecontainer.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- *  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 PARTICLECONTAINER_H
-#define 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  // PARTICLECONTAINER_H
diff --git a/src/particleemitter.cpp b/src/particleemitter.cpp
deleted file mode 100644
index 924f2094d..000000000
--- a/src/particleemitter.cpp
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- *  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 "particleemitter.h"
-
-#include "animationparticle.h"
-#include "logger.h"
-#include "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/particleemitter.h b/src/particleemitter.h
deleted file mode 100644
index 28b894254..000000000
--- a/src/particleemitter.h
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- *  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 PARTICLEEMITTER_H
-#define PARTICLEEMITTER_H
-
-#include "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  // PARTICLEEMITTER_H
diff --git a/src/particleemitterprop.h b/src/particleemitterprop.h
deleted file mode 100644
index 6196a1eca..000000000
--- a/src/particleemitterprop.h
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- *  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 PARTICLEEMITTERPROP_H
-#define 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  // PARTICLEEMITTERPROP_H
diff --git a/src/rotationalparticle.cpp b/src/rotationalparticle.cpp
deleted file mode 100644
index af04df1e7..000000000
--- a/src/rotationalparticle.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- *  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 "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/rotationalparticle.h b/src/rotationalparticle.h
deleted file mode 100644
index d0108e530..000000000
--- a/src/rotationalparticle.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- *  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 ROTATIONALPARTICLE_H
-#define ROTATIONALPARTICLE_H
-
-#include "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  // ROTATIONALPARTICLE_H
diff --git a/src/statuseffect.h b/src/statuseffect.h
index 15b0bd1e1..0a38e54f6 100644
--- a/src/statuseffect.h
+++ b/src/statuseffect.h
@@ -23,9 +23,10 @@
 #ifndef STATUSEFFECT_H
 #define STATUSEFFECT_H
 
-#include "particle.h"
 #include "animatedsprite.h"
 
+#include "particle/particle.h"
+
 #include "resources/animation.h"
 
 class StatusEffect final
diff --git a/src/textparticle.cpp b/src/textparticle.cpp
deleted file mode 100644
index c573ca567..000000000
--- a/src/textparticle.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- *  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 "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/textparticle.h b/src/textparticle.h
deleted file mode 100644
index f48a57196..000000000
--- a/src/textparticle.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- *  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 TEXTPARTICLE_H
-#define TEXTPARTICLE_H
-
-#include "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  // TEXTPARTICLE_H
-- 
cgit v1.2.3-70-g09d2