diff options
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/being/actorsprite.cpp | 2 | ||||
-rw-r--r-- | src/being/being.cpp | 9 | ||||
-rw-r--r-- | src/client.cpp | 3 | ||||
-rw-r--r-- | src/game.cpp | 15 | ||||
-rw-r--r-- | src/gui/widgets/tabs/debugwindowtabs.cpp | 2 | ||||
-rw-r--r-- | src/net/eathena/beingrecv.cpp | 4 | ||||
-rw-r--r-- | src/net/tmwa/beingrecv.cpp | 2 | ||||
-rw-r--r-- | src/particle/particle.cpp | 33 | ||||
-rw-r--r-- | src/particle/particle.h | 26 | ||||
-rw-r--r-- | src/particle/particleemitter.cpp | 2 | ||||
-rw-r--r-- | src/particle/particleengine.cpp | 558 | ||||
-rw-r--r-- | src/particle/particleengine.h | 353 | ||||
-rw-r--r-- | src/resources/map/map.cpp | 19 | ||||
-rw-r--r-- | src/resources/map/map.h | 3 |
16 files changed, 958 insertions, 77 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a24e18d1..2431c7584 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1098,6 +1098,8 @@ SET(SRCS particle/particleemitter.cpp particle/particleemitter.h particle/particleemitterprop.h + particle/particleengine.cpp + particle/particleengine.h particle/particleinfo.h particle/particlelist.cpp particle/particlelist.h diff --git a/src/Makefile.am b/src/Makefile.am index 863208eb1..e97b6ea85 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1129,6 +1129,8 @@ manaplus_SOURCES += main.cpp \ particle/particleemitter.cpp \ particle/particleemitter.h \ particle/particleemitterprop.h \ + particle/particleengine.cpp \ + particle/particleengine.h \ particle/particleinfo.h \ particle/particlelist.cpp \ particle/particlelist.h \ diff --git a/src/being/actorsprite.cpp b/src/being/actorsprite.cpp index 41f7d92bd..629fef7b6 100644 --- a/src/being/actorsprite.cpp +++ b/src/being/actorsprite.cpp @@ -318,7 +318,7 @@ void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display, mChildParticleEffects.clear(); // setup particle effects - if (Particle::enabled && particleEngine) + if (ParticleEngine::enabled && particleEngine) { FOR_EACH (StringVectCIter, itr, display.particles) { diff --git a/src/being/being.cpp b/src/being/being.cpp index ccd28c79f..b24f10ecd 100644 --- a/src/being/being.cpp +++ b/src/being/being.cpp @@ -1498,7 +1498,7 @@ void Being::setAction(const BeingActionT &restrict action, reset(); // attack particle effect - if (Particle::enabled && effectManager) + if (ParticleEngine::enabled && effectManager) { const int effectId = mInfo->getAttack(attackId)->mEffectId; if (effectId >= 0) @@ -3879,7 +3879,7 @@ void Being::removeAfkEffect() restrict2 void Being::addSpecialEffect(const int effect) restrict2 { if (effectManager && - Particle::enabled && + ParticleEngine::enabled && !mSpecialParticle && effect != -1) { @@ -4179,7 +4179,8 @@ void Being::addItemParticles(const int id, return; // setup particle effects - if (Particle::enabled && particleEngine) + if (ParticleEngine::enabled && + particleEngine) { FOR_EACH (StringVectCIter, itr, display.particles) { @@ -4479,7 +4480,7 @@ void Being::setTrickDead(const bool b) restrict2 void Being::setSpiritBalls(const unsigned int balls) restrict2 { - if (!Particle::enabled) + if (!ParticleEngine::enabled) { mSpiritBalls = balls; return; diff --git a/src/client.cpp b/src/client.cpp index 201700038..07b35b6f8 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1720,7 +1720,8 @@ void Client::optionChanged(const std::string &name) } else if (name == "particleEmitterSkip") { - Particle::emitterSkip = config.getIntValue("particleEmitterSkip") + 1; + ParticleEngine::emitterSkip = + config.getIntValue("particleEmitterSkip") + 1; } else if (name == "vsync") { diff --git a/src/game.cpp b/src/game.cpp index 40d5478af..c59103bb6 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -41,7 +41,7 @@ #include "enums/being/beingdirection.h" -#include "particle/particle.h" +#include "particle/particleengine.h" #include "input/inputmanager.h" #include "input/joystick.h" @@ -165,7 +165,7 @@ static void initEngines() #endif crazyMoves = new CrazyMoves; - particleEngine = new Particle; + particleEngine = new ParticleEngine; particleEngine->setMap(nullptr); particleEngine->setupEngine(); BeingInfo::init(); @@ -777,9 +777,9 @@ void Game::adjustPerfomance() break; } case 2: - if (Particle::emitterSkip < 4) + if (ParticleEngine::emitterSkip < 4) { - Particle::emitterSkip = 4; + ParticleEngine::emitterSkip = 4; if (localChatTab) { localChatTab->chatLog("Auto lower Particle " @@ -826,14 +826,14 @@ void Game::resetAdjustLevel() case 2: config.setValue("beingopacity", config.getBoolValue("beingopacity")); - Particle::emitterSkip = config.getIntValue( + ParticleEngine::emitterSkip = config.getIntValue( "particleEmitterSkip") + 1; break; default: case 3: config.setValue("beingopacity", config.getBoolValue("beingopacity")); - Particle::emitterSkip = config.getIntValue( + ParticleEngine::emitterSkip = config.getIntValue( "particleEmitterSkip") + 1; config.setValue("alphaCache", config.getBoolValue("alphaCache")); @@ -1074,8 +1074,7 @@ void Game::changeMap(const std::string &mapPath) // Initialize map-based particle effects if (newMap) - newMap->initializeParticleEffects(particleEngine); - + newMap->initializeParticleEffects(); // Start playing new music file when necessary const std::string oldMusic = mCurrentMap diff --git a/src/gui/widgets/tabs/debugwindowtabs.cpp b/src/gui/widgets/tabs/debugwindowtabs.cpp index 761ee93e4..fc22ada18 100644 --- a/src/gui/widgets/tabs/debugwindowtabs.cpp +++ b/src/gui/widgets/tabs/debugwindowtabs.cpp @@ -205,7 +205,7 @@ void MapDebugTab::logic() mUpdateTime = cur_time; // TRANSLATORS: debug window label mParticleCountLabel->setCaption(strprintf(_("Particle count: %d"), - Particle::particleCount)); + ParticleEngine::particleCount)); mMapActorCountLabel->setCaption( // TRANSLATORS: debug window label diff --git a/src/net/eathena/beingrecv.cpp b/src/net/eathena/beingrecv.cpp index 18c8c11b5..8be5df894 100644 --- a/src/net/eathena/beingrecv.cpp +++ b/src/net/eathena/beingrecv.cpp @@ -1140,7 +1140,7 @@ void BeingRecv::processBeingSpecialEffect(Net::MessageIn &msg) const int effectType = msg.readInt32("effect type"); - if (Particle::enabled) + if (ParticleEngine::enabled) effectManager->trigger(effectType, being); // +++ need dehard code effectType == 3 @@ -1395,7 +1395,7 @@ void BeingRecv::processBeingSelfEffect(Net::MessageIn &msg) } const int effectType = msg.readInt32("effect type"); - if (Particle::enabled) + if (ParticleEngine::enabled) effectManager->trigger(effectType, being); BLOCK_END("BeingRecv::processBeingSelfEffect") diff --git a/src/net/tmwa/beingrecv.cpp b/src/net/tmwa/beingrecv.cpp index 6d473a853..d695e1734 100644 --- a/src/net/tmwa/beingrecv.cpp +++ b/src/net/tmwa/beingrecv.cpp @@ -1286,7 +1286,7 @@ void BeingRecv::processBeingSelfEffect(Net::MessageIn &msg) const int effectType = msg.readInt32("effect type"); - if (Particle::enabled) + if (ParticleEngine::enabled) effectManager->trigger(effectType, being); // +++ need dehard code effectType == 3 diff --git a/src/particle/particle.cpp b/src/particle/particle.cpp index 4e583d0ab..8f0cb1e41 100644 --- a/src/particle/particle.cpp +++ b/src/particle/particle.cpp @@ -41,20 +41,11 @@ #include "debug.h" -Particle *particleEngine = nullptr; - static const float SIN45 = 0.707106781F; class Graphics; class Image; -int Particle::particleCount = 0; -int Particle::maxCount = 0; -int Particle::fastPhysics = 0; -int Particle::emitterSkip = 1; -bool Particle::enabled = true; -const float Particle::PARTICLE_SKY = 800.0F; - Particle::Particle() : Actor(), mAlpha(1.0F), @@ -79,26 +70,14 @@ Particle::Particle() : mAllowSizeAdjust(false), mFollow(false) { - Particle::particleCount++; + ParticleEngine::particleCount++; } Particle::~Particle() { // Delete child emitters and child particles clear(); - Particle::particleCount--; -} - -void Particle::setupEngine() restrict2 -{ - Particle::maxCount = config.getIntValue("particleMaxCount"); - Particle::fastPhysics = config.getIntValue("particleFastPhysics"); - Particle::emitterSkip = config.getIntValue("particleEmitterSkip") + 1; - if (!Particle::emitterSkip) - Particle::emitterSkip = 1; - Particle::enabled = config.getBoolValue("particleeffects"); - disableAutoDelete(); - logger->log1("Particle engine set up"); + ParticleEngine::particleCount--; } void Particle::draw(Graphics *restrict const, @@ -123,7 +102,7 @@ void Particle::updateSelf() restrict2 dist.x *= SIN45; float invHypotenuse; - switch (Particle::fastPhysics) + switch (ParticleEngine::fastPhysics) { case 1: invHypotenuse = fastInvSqrt( @@ -191,14 +170,14 @@ void Particle::updateSelf() restrict2 mAlive = AliveStatus::DEAD_FLOOR; } } - else if (mPos.z > PARTICLE_SKY) + else if (mPos.z > ParticleEngine::PARTICLE_SKY) { mAlive = AliveStatus::DEAD_SKY; } // Update child emitters - if (Particle::emitterSkip && (mLifetimePast - 1) - % Particle::emitterSkip == 0) + if (ParticleEngine::emitterSkip && + (mLifetimePast - 1) % ParticleEngine::emitterSkip == 0) { FOR_EACH (EmitterConstIterator, e, mChildEmitters) { diff --git a/src/particle/particle.h b/src/particle/particle.h index 323e1c171..01fea38e0 100644 --- a/src/particle/particle.h +++ b/src/particle/particle.h @@ -27,6 +27,8 @@ #include "enums/particle/alivestatus.h" +#include "particle/particleengine.h" + #include "localconsts.h" class Color; @@ -34,27 +36,13 @@ class Font; class Particle; class ParticleEmitter; -typedef std::list<Particle *> Particles; -typedef Particles::iterator ParticleIterator; -typedef Particles::const_iterator ParticleConstIterator; -typedef std::list<ParticleEmitter *> Emitters; -typedef Emitters::iterator EmitterIterator; -typedef Emitters::const_iterator EmitterConstIterator; - /** * A particle spawned by a ParticleEmitter. */ class Particle notfinal : public Actor { public: - static const float PARTICLE_SKY; // Maximum Z position of particles - static int fastPhysics; // Mode of squareroot calculation - static int particleCount; // Current number of particles - static int maxCount; // Maximum number of particles - static int emitterSkip; // Duration of pause between two - // emitter updates in ticks - static bool enabled; // true when non-crucial particle effects - // are disabled + friend class ParticleEngine; Particle(); @@ -71,12 +59,6 @@ class Particle notfinal : public Actor void clear() restrict2; /** - * Gives a particle the properties of an engine root particle and loads - * the particle-related config settings. - */ - void setupEngine() restrict2; - - /** * Updates particle position, returns false when the particle should * be deleted. */ @@ -351,6 +333,4 @@ class Particle notfinal : public Actor bool mFollow; }; -extern Particle *particleEngine; - #endif // PARTICLE_PARTICLE_H diff --git a/src/particle/particleemitter.cpp b/src/particle/particleemitter.cpp index d82282060..44486e98e 100644 --- a/src/particle/particleemitter.cpp +++ b/src/particle/particleemitter.cpp @@ -508,7 +508,7 @@ void ParticleEmitter::createParticles(const int tick, for (int i = mOutput.value(tick); i > 0; i--) { // Limit maximum particles - if (Particle::particleCount > Particle::maxCount) + if (ParticleEngine::particleCount > ParticleEngine::maxCount) break; Particle *newParticle = nullptr; diff --git a/src/particle/particleengine.cpp b/src/particle/particleengine.cpp new file mode 100644 index 000000000..1a65b51d8 --- /dev/null +++ b/src/particle/particleengine.cpp @@ -0,0 +1,558 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2016 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "particle/particle.h" + +#include "configuration.h" +#include "logger.h" + +#include "gui/viewport.h" + +#include "particle/animationparticle.h" +#include "particle/particleemitter.h" +#include "particle/rotationalparticle.h" +#include "particle/textparticle.h" + +#include "resources/resourcemanager.h" + +#include "resources/dye/dye.h" + +#include "utils/dtor.h" +#include "utils/mathutils.h" + +#include "debug.h" + +ParticleEngine *particleEngine = nullptr; + +static const float SIN45 = 0.707106781F; + +class Graphics; +class Image; + +int ParticleEngine::particleCount = 0; +int ParticleEngine::maxCount = 0; +int ParticleEngine::fastPhysics = 0; +int ParticleEngine::emitterSkip = 1; +bool ParticleEngine::enabled = true; +const float ParticleEngine::PARTICLE_SKY = 800.0F; + +ParticleEngine::ParticleEngine() : + Actor(), + mAlpha(1.0F), + mLifetimeLeft(-1), + mLifetimePast(0), + mFadeOut(0), + mFadeIn(0), + mVelocity(), + mAlive(AliveStatus::ALIVE), + mChildEmitters(), + mChildParticles(), + mDeathEffect(), + mGravity(0.0F), + mBounce(0.0F), + mAcceleration(0.0F), + mInvDieDistance(-1.0F), + mMomentum(1.0F), + mTarget(nullptr), + mRandomness(0), + mDeathEffectConditions(0x00), + mAutoDelete(true), + mAllowSizeAdjust(false), + mFollow(false) +{ + ParticleEngine::particleCount++; +} + +ParticleEngine::~ParticleEngine() +{ + // Delete child emitters and child particles + clear(); + ParticleEngine::particleCount--; +} + +void ParticleEngine::setupEngine() restrict2 +{ + ParticleEngine::maxCount = config.getIntValue("particleMaxCount"); + ParticleEngine::fastPhysics = config.getIntValue("particleFastPhysics"); + ParticleEngine::emitterSkip = config.getIntValue("particleEmitterSkip") + 1; + if (!ParticleEngine::emitterSkip) + ParticleEngine::emitterSkip = 1; + ParticleEngine::enabled = config.getBoolValue("particleeffects"); + disableAutoDelete(); + logger->log1("Particle engine set up"); +} + +void ParticleEngine::draw(Graphics *restrict const, + const int, const int) const restrict2 +{ +} + +void ParticleEngine::updateSelf() restrict2 +{ + if (mLifetimeLeft == 0 && mAlive == AliveStatus::ALIVE) + mAlive = AliveStatus::DEAD_TIMEOUT; + + if (mAlive == AliveStatus::ALIVE) + { + // calculate particle movement + if (mMomentum != 1.0F) + mVelocity *= mMomentum; + + if (mTarget && mAcceleration != 0.0F) + { + Vector dist = mPos - mTarget->mPos; + dist.x *= SIN45; + float invHypotenuse; + + switch (ParticleEngine::fastPhysics) + { + case 1: + invHypotenuse = fastInvSqrt( + dist.x * dist.x + dist.y * dist.y + dist.z * dist.z); + break; + case 2: + if (!dist.x) + { + invHypotenuse = 0; + break; + } + + invHypotenuse = 2.0F / (static_cast<float>(fabs(dist.x)) + + static_cast<float>(fabs(dist.y)) + + static_cast<float>(fabs(dist.z))); + break; + default: + invHypotenuse = 1.0F / static_cast<float>(sqrt( + dist.x * dist.x + dist.y * dist.y + dist.z * dist.z)); + break; + } + + if (invHypotenuse) + { + if (mInvDieDistance > 0.0F && invHypotenuse > mInvDieDistance) + mAlive = AliveStatus::DEAD_IMPACT; + const float accFactor = invHypotenuse * mAcceleration; + mVelocity -= dist * accFactor; + } + } + + if (mRandomness > 0) + { + mVelocity.x += static_cast<float>((rand() % mRandomness - rand() + % mRandomness)) / 1000.0F; + mVelocity.y += static_cast<float>((rand() % mRandomness - rand() + % mRandomness)) / 1000.0F; + mVelocity.z += static_cast<float>((rand() % mRandomness - rand() + % mRandomness)) / 1000.0F; + } + + mVelocity.z -= mGravity; + + // Update position + mPos.x += mVelocity.x; + mPos.y += mVelocity.y * SIN45; + mPos.z += mVelocity.z * SIN45; + + // Update other stuff + if (mLifetimeLeft > 0) + mLifetimeLeft--; + + mLifetimePast++; + + if (mPos.z < 0.0F) + { + if (mBounce > 0.0F) + { + mPos.z *= -mBounce; + mVelocity *= mBounce; + mVelocity.z = -mVelocity.z; + } + else + { + mAlive = AliveStatus::DEAD_FLOOR; + } + } + else if (mPos.z > PARTICLE_SKY) + { + mAlive = AliveStatus::DEAD_SKY; + } + + // Update child emitters + if (ParticleEngine::emitterSkip && (mLifetimePast - 1) + % ParticleEngine::emitterSkip == 0) + { + FOR_EACH (EmitterConstIterator, e, mChildEmitters) + { + std::vector<Particle*> newParticles; + (*e)->createParticles(mLifetimePast, newParticles); + FOR_EACH (std::vector<Particle*>::const_iterator, + it, + newParticles) + { + Particle *const p = *it; + p->moveBy(mPos); + mChildParticles.push_back(p); + } + } + } + } + + // create death effect when the particle died + if (mAlive != AliveStatus::ALIVE && + mAlive != AliveStatus::DEAD_LONG_AGO) + { + if ((CAST_U32(mAlive) & mDeathEffectConditions) + > 0x00 && !mDeathEffect.empty()) + { + Particle *restrict const deathEffect = particleEngine->addEffect( + mDeathEffect, 0, 0); + if (deathEffect) + deathEffect->moveBy(mPos); + } + mAlive = AliveStatus::DEAD_LONG_AGO; + } +} + +bool ParticleEngine::update() restrict2 +{ + if (!mMap) + return false; + + const Vector oldPos = mPos; + + updateSelf(); + + const Vector change = mPos - oldPos; + + if (mChildParticles.empty()) + { + if (mAlive != AliveStatus::ALIVE && + mAutoDelete) + { + return false; + } + return true; + } + + // Update child particles + + const int cameraX = viewport->getCameraX(); + const int cameraY = viewport->getCameraY(); + const float x1 = static_cast<float>(cameraX - 3000); + const float y1 = static_cast<float>(cameraY - 2000); + const float x2 = static_cast<float>(cameraX + 3000); + const float y2 = static_cast<float>(cameraY + 2000); + + for (ParticleIterator p = mChildParticles.begin(), + fp2 = mChildParticles.end(); p != fp2; ) + { + Particle *restrict const particle = *p; + const float posX = particle->mPos.x; + const float posY = particle->mPos.y; + if (posX < x1 || posX > x2 || posY < y1 || posY > y2) + { + ++p; + continue; + } + // move particle with its parent if desired + if (particle->mFollow) + particle->moveBy(change); + + // update particle + if (particle->update()) + { + ++p; + } + else + { + delete particle; + p = mChildParticles.erase(p); + } + } + if (mAlive != AliveStatus::ALIVE && + mChildParticles.empty() && + mAutoDelete) + { + return false; + } + + return true; +} + +void ParticleEngine::moveBy(const Vector &restrict change) restrict2 +{ + mPos += change; + FOR_EACH (ParticleConstIterator, p, mChildParticles) + { + Particle *restrict const particle = *p; + if (particle->mFollow) + particle->moveBy(change); + } +} + +void ParticleEngine::moveTo(const float x, const float y) restrict2 +{ + moveTo(Vector(x, y, mPos.z)); +} + +Particle *ParticleEngine::createChild() restrict2 +{ + Particle *const newParticle = new Particle(); + newParticle->setMap(mMap); + mChildParticles.push_back(newParticle); + return newParticle; +} + +Particle *ParticleEngine::addEffect(const std::string &restrict + particleEffectFile, + const int pixelX, + const int pixelY, + const int rotation) restrict2 +{ + Particle *newParticle = nullptr; + + const size_t pos = particleEffectFile.find('|'); + const std::string dyePalettes = (pos != std::string::npos) + ? particleEffectFile.substr(pos + 1) : ""; + XML::Document doc(particleEffectFile.substr(0, pos), + UseResman_true, + SkipError_false); + const XmlNodePtrConst rootNode = doc.rootNode(); + + if (!rootNode || !xmlNameEqual(rootNode, "effect")) + { + logger->log("Error loading particle: %s", particleEffectFile.c_str()); + return nullptr; + } + + // Parse particles + for_each_xml_child_node(effectChildNode, rootNode) + { + // We're only interested in particles + if (!xmlNameEqual(effectChildNode, "particle")) + continue; + + // Determine the exact particle type + XmlNodePtr node; + + // Animation + if ((node = XML::findFirstChildByName(effectChildNode, "animation"))) + { + newParticle = new AnimationParticle(node, dyePalettes); + newParticle->setMap(mMap); + } + // Rotational + else if ((node = XML::findFirstChildByName( + effectChildNode, "rotation"))) + { + newParticle = new RotationalParticle(node, dyePalettes); + newParticle->setMap(mMap); + } + // Image + else if ((node = XML::findFirstChildByName(effectChildNode, "image"))) + { + std::string imageSrc; + if (XmlHaveChildContent(node)) + imageSrc = XmlChildContent(node); + if (!imageSrc.empty() && !dyePalettes.empty()) + Dye::instantiate(imageSrc, dyePalettes); + Image *const img = resourceManager->getImage(imageSrc); + + newParticle = new ImageParticle(img); + newParticle->setMap(mMap); + } + // Other + else + { + newParticle = new Particle(); + newParticle->setMap(mMap); + } + + // Read and set the basic properties of the particle + const float offsetX = static_cast<float>(XML::getFloatProperty( + effectChildNode, "position-x", 0)); + const float offsetY = static_cast<float>(XML::getFloatProperty( + effectChildNode, "position-y", 0)); + const float offsetZ = static_cast<float>(XML::getFloatProperty( + effectChildNode, "position-z", 0)); + const Vector position(mPos.x + static_cast<float>(pixelX) + offsetX, + mPos.y + static_cast<float>(pixelY) + offsetY, + mPos.z + offsetZ); + newParticle->moveTo(position); + + const int lifetime = XML::getProperty(effectChildNode, "lifetime", -1); + newParticle->setLifetime(lifetime); + const bool resizeable = "false" != XML::getProperty(effectChildNode, + "size-adjustable", "false"); + + newParticle->setAllowSizeAdjust(resizeable); + + // Look for additional emitters for this particle + for_each_xml_child_node(emitterNode, effectChildNode) + { + if (xmlNameEqual(emitterNode, "emitter")) + { + ParticleEmitter *restrict const newEmitter = + new ParticleEmitter( + emitterNode, + newParticle, + mMap, + rotation, + dyePalettes); + newParticle->addEmitter(newEmitter); + } + else if (xmlNameEqual(emitterNode, "deatheffect")) + { + std::string deathEffect; + if (node && XmlHaveChildContent(node)) + deathEffect = XmlChildContent(emitterNode); + + char deathEffectConditions = 0x00; + if (XML::getBoolProperty(emitterNode, "on-floor", true)) + { + deathEffectConditions += CAST_S8( + AliveStatus::DEAD_FLOOR); + } + if (XML::getBoolProperty(emitterNode, "on-sky", true)) + { + deathEffectConditions += CAST_S8( + AliveStatus::DEAD_SKY); + } + if (XML::getBoolProperty(emitterNode, "on-other", false)) + { + deathEffectConditions += CAST_S8( + AliveStatus::DEAD_OTHER); + } + if (XML::getBoolProperty(emitterNode, "on-impact", true)) + { + deathEffectConditions += CAST_S8( + AliveStatus::DEAD_IMPACT); + } + if (XML::getBoolProperty(emitterNode, "on-timeout", true)) + { + deathEffectConditions += CAST_S8( + AliveStatus::DEAD_TIMEOUT); + } + newParticle->setDeathEffect( + deathEffect, deathEffectConditions); + } + } + + mChildParticles.push_back(newParticle); + } + + return newParticle; +} + +Particle *ParticleEngine::addTextSplashEffect(const std::string &restrict text, + const int x, + const int y, + const Color *restrict const + color, + Font *restrict const font, + const bool outline) restrict2 +{ + Particle *const newParticle = new TextParticle( + text, + color, + font, + outline); + newParticle->setMap(mMap); + newParticle->moveTo(static_cast<float>(x), + static_cast<float>(y)); + newParticle->setVelocity( + static_cast<float>((rand() % 100) - 50) / 200.0F, // X + static_cast<float>((rand() % 100) - 50) / 200.0F, // Y + (static_cast<float>((rand() % 100)) / 200.0F) + 4.0F); // Z + + newParticle->setGravity(0.1F); + newParticle->setBounce(0.5F); + newParticle->setLifetime(200); + newParticle->setFadeOut(100); + + mChildParticles.push_back(newParticle); + + return newParticle; +} + +Particle *ParticleEngine::addTextRiseFadeOutEffect(const std::string &restrict + text, + const int x, + const int y, + const Color *restrict const + color, + Font *restrict const font, + const bool outline) restrict2 +{ + Particle *const newParticle = new TextParticle( + text, + color, + font, + outline); + newParticle->setMap(mMap); + newParticle->moveTo(static_cast<float>(x), + static_cast<float>(y)); + newParticle->setVelocity(0.0F, 0.0F, 0.5F); + newParticle->setGravity(0.0015F); + newParticle->setLifetime(300); + newParticle->setFadeOut(100); + newParticle->setFadeIn(0); + + mChildParticles.push_back(newParticle); + + return newParticle; +} + +void ParticleEngine::adjustEmitterSize(const int w, const int h) restrict2 +{ + if (mAllowSizeAdjust) + { + FOR_EACH (EmitterConstIterator, e, mChildEmitters) + (*e)->adjustSize(w, h); + } +} + +void ParticleEngine::prepareToDie() restrict2 +{ + FOR_EACH (ParticleIterator, p, mChildParticles) + { + Particle *restrict const particle = *p; + if (!particle) + continue; + particle->prepareToDie(); + if (particle->isAlive() && + particle->mLifetimeLeft == -1 && + particle->mAutoDelete) + { + particle->kill(); + } + } +} + +void ParticleEngine::clear() restrict2 +{ + delete_all(mChildEmitters); + mChildEmitters.clear(); + + delete_all(mChildParticles); + mChildParticles.clear(); +} diff --git a/src/particle/particleengine.h b/src/particle/particleengine.h new file mode 100644 index 000000000..8916c0ac1 --- /dev/null +++ b/src/particle/particleengine.h @@ -0,0 +1,353 @@ +/* + * The ManaPlus Client + * Copyright (C) 2006-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2016 The ManaPlus Developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PARTICLE_PARTICLEENGINE_H +#define PARTICLE_PARTICLEENGINE_H + +#include "being/actor.h" + +#include "enums/particle/alivestatus.h" + +#include "localconsts.h" + +class Color; +class Font; +class Particle; +class ParticleEmitter; + +typedef std::list<Particle *> Particles; +typedef Particles::iterator ParticleIterator; +typedef Particles::const_iterator ParticleConstIterator; +typedef std::list<ParticleEmitter *> Emitters; +typedef Emitters::iterator EmitterIterator; +typedef Emitters::const_iterator EmitterConstIterator; + +class ParticleEngine final : public Actor +{ + public: + static const float PARTICLE_SKY; // Maximum Z position of particles + static int fastPhysics; // Mode of squareroot calculation + static int particleCount; // Current number of particles + static int maxCount; // Maximum number of particles + static int emitterSkip; // Duration of pause between two + // emitter updates in ticks + static bool enabled; // true when non-crucial particle effects + // are disabled + + ParticleEngine(); + + A_DELETE_COPY(ParticleEngine) + + /** + * Destructor. + */ + ~ParticleEngine(); + + /** + * Deletes all child particles and emitters. + */ + void clear() restrict2; + + /** + * Gives a particle the properties of an engine root particle and loads + * the particle-related config settings. + */ + void setupEngine() restrict2; + + /** + * Updates particle position, returns false when the particle should + * be deleted. + */ + bool update() restrict2; + + /** + * Draws the particle image. + */ + void draw(Graphics *restrict const graphics, + const int offsetX, + const int offsetY) const restrict2 override + A_CONST A_NONNULL(2); + + /** + * Necessary for sorting with the other sprites. + */ + int getPixelY() const restrict2 override A_WARN_UNUSED + { return CAST_S32(mPos.y) - 16; } + + /** + * Necessary for sorting with the other sprites for sorting only. + */ + int getSortPixelY() const restrict2 override A_WARN_UNUSED + { return CAST_S32(mPos.y) - 16; } + + /** + * Creates a blank particle as a child of the current particle + * Useful for creating target particles + */ + Particle *createChild() restrict2; + + /** + * Creates a child particle that hosts some emitters described in the + * particleEffectFile. + */ + Particle *addEffect(const std::string &restrict particleEffectFile, + const int pixelX, const int pixelY, + const int rotation = 0) restrict2; + + /** + * Creates a standalone text particle. + */ + Particle *addTextSplashEffect(const std::string &restrict text, + const int x, const int y, + const Color *restrict const color, + Font *restrict const font, + const bool outline = false) + restrict2 A_NONNULL(5, 6); + + /** + * Creates a standalone text particle. + */ + Particle *addTextRiseFadeOutEffect(const std::string &restrict text, + const int x, const int y, + const Color *restrict const color, + Font *restrict const font, + const bool outline = false) + restrict2 A_NONNULL(5, 6); + + /** + * Adds an emitter to the particle. + */ + void addEmitter(ParticleEmitter *const emitter) restrict2 A_NONNULL(2) + { mChildEmitters.push_back(emitter); } + + /** + * Sets the position in 3 dimensional space in pixels relative to map. + */ + void moveTo(const Vector &restrict pos) restrict2 + { moveBy(pos - mPos); } + + /** + * Sets the position in 2 dimensional space in pixels relative to map. + */ + void moveTo(const float x, const float y) restrict2; + + /** + * Changes the particle position relative + */ + void moveBy(const Vector &restrict change) restrict2; + + /** + * Sets the time in game ticks until the particle is destroyed. + */ + void setLifetime(const int lifetime) restrict2 noexcept + { mLifetimeLeft = lifetime; mLifetimePast = 0; } + + /** + * Sets the age of the pixel in game ticks where the particle has + * faded in completely. + */ + void setFadeOut(const int fadeOut) restrict2 noexcept + { mFadeOut = fadeOut; } + + /** + * Sets the remaining particle lifetime where the particle starts to + * fade out. + */ + void setFadeIn(const int fadeIn) restrict2 noexcept + { mFadeIn = fadeIn; } + + /** + * Sets the current velocity in 3 dimensional space. + */ + void setVelocity(const float x, + const float y, + const float z) restrict2 noexcept + { mVelocity.x = x; mVelocity.y = y; mVelocity.z = z; } + + /** + * Sets the downward acceleration. + */ + void setGravity(const float gravity) restrict2 noexcept + { mGravity = gravity; } + + /** + * Sets the ammount of random vector changes + */ + void setRandomness(const int r) restrict2 noexcept + { mRandomness = r; } + + /** + * Sets the ammount of velocity particles retain after + * hitting the ground. + */ + void setBounce(const float bouncieness) restrict2 noexcept + { mBounce = bouncieness; } + + /** + * Sets the flag if the particle is supposed to be moved by its parent + */ + void setFollow(const bool follow) restrict2 noexcept + { mFollow = follow; } + + /** + * Gets the flag if the particle is supposed to be moved by its parent + */ + bool doesFollow() const restrict2 noexcept A_WARN_UNUSED + { return mFollow; } + + /** + * Makes the particle move toward another particle with a + * given acceleration and momentum + */ + void setDestination(Particle *restrict const target, + const float accel, + const float moment) restrict2 noexcept A_NONNULL(2) + { mTarget = target; mAcceleration = accel; mMomentum = moment; } + + /** + * Sets the distance in pixel the particle can come near the target + * particle before it is destroyed. Does only make sense after a target + * particle has been set using setDestination. + */ + void setDieDistance(const float dist) restrict2 + { mInvDieDistance = 1.0F / dist; } + + /** + * Changes the size of the emitters so that the effect fills a + * rectangle of this size + */ + void adjustEmitterSize(const int w, const int h) restrict2; + + void setAllowSizeAdjust(const bool adjust) restrict2 noexcept + { mAllowSizeAdjust = adjust; } + + bool isAlive() const restrict2 noexcept A_WARN_UNUSED + { return mAlive == AliveStatus::ALIVE; } + + void prepareToDie() restrict2; + + /** + * Determines whether the particle and its children are all dead + */ + bool isExtinct() const restrict2 noexcept A_WARN_UNUSED + { return !isAlive() && mChildParticles.empty(); } + + /** + * Manually marks the particle for deletion. + */ + void kill() restrict2 noexcept + { mAlive = AliveStatus::DEAD_OTHER; mAutoDelete = true; } + + /** + * After calling this function the particle will only request + * deletion when kill() is called + */ + void disableAutoDelete() restrict2 noexcept + { mAutoDelete = false; } + + /** We consider particles (at least for now) to be one layer-sprites */ + int getNumberOfLayers() const restrict2 override final + { return 1; } + + float getAlpha() const restrict2 override final + { return 1.0F; } + + void setAlpha(const float alpha A_UNUSED) restrict2 override + { } + + void setDeathEffect(const std::string &restrict effectFile, + const signed char conditions) restrict2 + { mDeathEffect = effectFile; mDeathEffectConditions = conditions; } + + protected: + void updateSelf() restrict2; + + // Opacity of the graphical representation of the particle + float mAlpha; + + // Lifetime left in game ticks + int mLifetimeLeft; + + // Age of the particle in game ticks + int mLifetimePast; + + // Lifetime in game ticks left where fading out begins + int mFadeOut; + + // Age in game ticks where fading in is finished + int mFadeIn; + + // Speed in pixels per game-tick. + Vector mVelocity; + + // Is the particle supposed to be drawn and updated? + AliveStatusT mAlive; + private: + // List of child emitters. + Emitters mChildEmitters; + + // List of particles controlled by this particle + Particles mChildParticles; + + // Particle effect file to be spawned when the particle dies + std::string mDeathEffect; + + // dynamic particle + // Downward acceleration in pixels per game-tick. + float mGravity; + + // How much the particle bounces off when hitting the ground + float mBounce; + + // Acceleration towards the target particle in pixels per game-tick + float mAcceleration; + + // Distance in pixels from the target particle that causes + // the destruction of the particle + float mInvDieDistance; + + // How much speed the particle retains after each game tick + float mMomentum; + + // The particle that attracts this particle + Particle *restrict mTarget; + + // Ammount of random vector change + int mRandomness; + + // Bitfield of death conditions which trigger spawning + // of the death particle + signed char mDeathEffectConditions; + + // May the particle request its deletion by the parent particle? + bool mAutoDelete; + + // Can the effect size be adjusted by the object props in the map file? + bool mAllowSizeAdjust; + + // is this particle moved when its parent particle moves? + bool mFollow; +}; + +extern ParticleEngine *particleEngine; + +#endif // PARTICLE_PARTICLEENGINE_H diff --git a/src/resources/map/map.cpp b/src/resources/map/map.cpp index ada25e3d6..93b950ba5 100644 --- a/src/resources/map/map.cpp +++ b/src/resources/map/map.cpp @@ -1067,11 +1067,10 @@ void Map::addParticleEffect(const std::string &effectFile, mParticleEffects.push_back(newEffect); } -void Map::initializeParticleEffects(Particle *restrict const engine) - const restrict2 +void Map::initializeParticleEffects() const restrict2 { BLOCK_START("Map::initializeParticleEffects") - if (!engine) + if (!particleEngine) { BLOCK_END("Map::initializeParticleEffects") return; @@ -1081,11 +1080,19 @@ void Map::initializeParticleEffects(Particle *restrict const engine) { for (std::vector<ParticleEffectData>::const_iterator i = mParticleEffects.begin(); - i != mParticleEffects.end(); ++i) + i != mParticleEffects.end(); + ++i) { - Particle *const p = engine->addEffect(i->file, i->x, i->y); - if (p && i->w > 0 && i->h > 0) + Particle *const p = particleEngine->addEffect( + i->file, + i->x, + i->y); + if (p && + i->w > 0 && + i->h > 0) + { p->adjustEmitterSize(i->w, i->h); + } } } BLOCK_END("Map::initializeParticleEffects") diff --git a/src/resources/map/map.h b/src/resources/map/map.h index 61d328313..1f92f5951 100644 --- a/src/resources/map/map.h +++ b/src/resources/map/map.h @@ -222,8 +222,7 @@ class Map final : public Properties, public ConfigListener /** * Initializes all added particle effects */ - void initializeParticleEffects(Particle *restrict const particleEngine) - const restrict2; + void initializeParticleEffects() const restrict2; /** * Adds a tile animation to the map |