Author Topic: Sub-explosions and you  (Read 3633 times)

I modified a fireball projectile I made a few months ago to create sub-explosions and I ran numerous tests on the thing, I'd like to share my findings to help you guys implement some of your own sub-explosions. They can be very, very pretty. Also the fireball script is included in the second post along with some pictures for your enjoyment.


This is a rather long report and side tutorial, as per usual I've gone a bit overboard, so please feel free to skip to any section you might be having difficulties with. If you have any questions at all don't be afraid to ask



Sub-explosions are created from a main, Parent or Mother explosion. The sub-explosions are sometimes referred to daughters. You can issue a maximum of 5 direct sub explosions from any other explosion. The notation is:

Code: [Select]
datablock ExplosionData(ParentExplosion)
{
...
   subExplosion[0] = DaughterExplosion1;
   subExplosion[1] = DaughterExplosion2;
   subExplosion[2] = DaughterExplosion3;
   subExplosion[3] = DaughterExplosion4;
   subExplosion[4] = DaughterExplosion5;
...
};


You are not limited to making 5 completely different sub-explosions, you may also refer a single explosion several times as a sub explosion:

Code: [Select]
datablock ExplosionData(ParentExplosion)
{
...
   subExplosion[0] = DaughterExplosion1;
   subExplosion[1] = DaughterExplosion1;
   subExplosion[2] = DaughterExplosion1;
   subExplosion[3] = DaughterExplosion2;
   subExplosion[4] = DaughterExplosion2;
...
};


Keep in mind, sub-explosions are Regular Explosions, taken from any explosionData datablocks. More or less if you create the explosion from, say, a projectile it will be identical to an explosion created from another explosion. The only difference is sub-explosions do not exhibit any radius damage or radius impulse. They are purely for show.



The common issue: Why they don't work half the time or at all

Sub-explosions are very finicky things, especially with timing. Here are the two universal sub-explosion rules:

Rule 1 - A sub-explosion will only be created if it's Final calculated delay (delayMS +/- delayVariance) lies within the parent explosion's lifetime range.
Rule 2 - A sub-explosion will only be created if it's own lifetimeMS is LARGER then it's delayMS

So what is rule 2 telling us? Quite simply, it's saying that all sub-explosions' lifetimes begin as soon as the parent is created. However, the misconception that comes with this is then that people will then think the sub-explosions need to have a lifetime equal or less then the parent's to work, this however is not true. Some examples,

This sub-explosion does NOT work, it breaks both rules

Parent explosion: Lasts 600 milliseconds (MS)
Sub-explosion: Delayed by 1000 MS, lifetimeMS is 700 (In reality, the effects would last -300 MS, impossible)

This sub-explosion does NOT work, it breaks rule 1

Parent explosion: Lasts 600 milliseconds (MS)
Sub-explosion: Delayed by 1000 MS, lifetimeMS is 1200 (In reality, the effects would last 200 MS)

This sub-explosion does NOT work, it breaks rule 2

Parent explosion: Lasts 1100 milliseconds (MS)
Sub-explosion: Delayed by 1000 MS, lifetimeMS is 700 (In reality, the effects would last -300 MS, impossible)

This sub-explosion does work

Parent explosion: lifetimeMS is 600 milliseconds (MS)
Sub-explosion: Delayed by 500 MS, lifetimeMS is 700 (In reality, the effects would last 200 MS)

This sub-explosion also works

Parent explosion: lifetimeMS is 500 milliseconds (MS)
Sub-explosion: Delayed by 500 MS, lifetimeMS is 700 (In reality, the effects would last 200 MS)


So in summary, make sure the sub-explosion's delay is lower or equal to both the Parent's lifetime and the sub-Explosion's lifetime. Follow these rules and your sub explosions will work without fail.


On a side note, a weird exception I came accross:
This sub-explosion also works (even though it breaks the first rule)

Parent explosion: lifetimeMS is 450 milliseconds (MS)
Sub-explosion: Delayed by 500 MS, lifetimeMS is 700 (In reality, the effects would last 200 MS)

I have conducted a few range tests on the oddball example to try to find out what the minimum delay range needs to be from the Parent's lifetime to create the Sub-Explosion, so far I haven't come to any conclusion, other then when the difference is around 200 the example no longer creates a Sub-Explosion. So maybe rule 1 and 2 are linked, but it would require some more tests and it gets too complicated for what you guys will ever need. Anyhow,



Chain Reactions, linking sub explosions to even more sub explosions

I know what you guys are thinking now, probably along the lines of "Oh wow are you serious?". Yes, it is possible to assign yet another set of Sub-Explosions to other Sub-Explosions. The notation is the same as you will see,

Code: [Select]
datablock ExplosionData(DaughterExplosion1)
{
...
   subExplosion[0] = DaughtersSubExplosion1;
   subExplosion[1] = DaughtersSubExplosion2;
   subExplosion[2] = DaughtersSubExplosion3;
   subExplosion[3] = DaughterExplosion2; //And for good measure you may also refer explosions from the Parent explosion
   subExplosion[4] = DaughterExplosion2;
...
};

These things multiply fast, if we have 5 Sub-Explosions from a main explosion, each off shooting into 5 other explosions, the grand total comes to 26 explosions (counting the initial parent).

Keep in mind, every sub explosion abides by Rules 1 and 2, as you go further down the chain it adds the limitations of the sub-explosion beforehand as well as the limitations of the main Parent that created the mess. Essentially,

Parent Explosion -> Sub-Explosion (Rule 1 and 2 for itself and Parent) -> Sub Sub-explosion (Rule 1 and 2 for itself, Sub-Explosion and Parent) -> and so on

You will probably never need to keep track of more then 2 sets of sub explosions, the first set is more then enough for a nice bang. The second is used if you want a really big complicated boom. It's not all that difficult to keep track either for that matter.

Some important Sub-Explosion functions

There are some functions provided within the explosionData datablock which can help in whatever you're trying to make with sub-explosions. Recall sub-explosions don't have their own datablock, they're explosionData types just like their parent explosion.

delayMS = ##;
The time delay in milliseconds until the sub explosion is created, or appears.

delayVariance = ##;
This varies the delay by adding or subtracting from it. The amount it adds or subtracts lies within the range from 0 to the value ## of the variance.

offset = #.##;
Determines the offset amount that this explosion will appear from the origin (works on both parent and referred sub-explosions). 0.5 is the horizontal length of a single brick stud.
An important note is that explosions using offset will not appear on the other side of any raycast-collision checking wall or terrain, this includes Bricks. Also, no sub-explosion is ever left out, so say you create an explosion with 4 sub explosions in a corner of a brick house, all 4 sub explosions will appear in that corner. I am not certain when the raycast check for this is done, but I assume it's as soon as the parent explosion is created (from observations with builds blown up) It's way simpler then all this, sub-explosions seem to appear above the explosion, in a cone-shaped radius of about 60 degrees Theta-wise. There is nothing special here.

Offset is NOT scaled down!! Through events or otherwise, so prepare in advance.



Having effects die out instead of dragging on with a long-lived parent/sub explosion

This is a rather important bit, as you might have realised that with rule 1 and 2 the parent explosion and the next ones in line (assuming you're using sub-sub-explosions) might have lifetimes that make the effects last a bit too long for anyone's tastes. Here's what you can do,

Emitters
Use lifetimeMS = ##; from the emitterdata in any emitter datablock you want to kill prematurely in an explosion. It rules over an explosion's lifetimeMS, so you can cease the emitter after a specified lifetime, unless of course the explosion dies first.
**Remember, particleEmitter = SomeEmitter; is the Bulk emitter, it creates a set number of particles with both particleData and emitterData characteristics and immediately dies. You only need to worry about emitters[0 ] through [3 ] as far as emitter duration goes.

Dynamic Light
A long-lasting light effect sucks most of the time, let's face it. You can remedy this by taking the Parent explosion's light away, making it much less vibrant, making it's lifetimeMS as short as possible while encompassing the sub-explosions, or giving the sub explosions some light instead. Or a combination of these, it all works.

camShake
This thing is annoying, don't use it. Or atleast make it brief. Anyhow sub-explosions can also use the camShake functions.



And that's all folks, I hope this helps some of you. Have fun experimenting.

Edit: 2 pass-bys isn't enough it seems, this thing is littered with grammatical errors. Sorry about that.
« Last Edit: June 25, 2009, 11:33:36 AM by Muffinmix »

Here's some pictures as proof of concept, the big fireball explosions:



Kind of messy, so I used smaller-scaled ones:





Here's the script for reference. Don't release this under any form without my consent please.

Code: [Select]
//Projectile_FireBall2.cs


datablock ParticleData(FireBall2ExplosionParticle)
{
   dragCoefficient      = 1;
   gravityCoefficient   = 0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 1400;
   lifetimeVarianceMS   = 400;
   textureName          = "base/data/particles/cloud";
   spinSpeed = 10.0;
   spinRandomMin = -50.0;
   spinRandomMax = 50.0;
   colors[0]     = "0.9 0.7 0.6 0.9";
   colors[1]     = "0.9 0.3 0.0 0.9";
   colors[2]     = "0.6 0.2 0.0 0.7";
   colors[3]     = "0.6 0.1 0.0 0.0";

   sizes[0]      = 8.0;
   sizes[1]      = 10.0;
   sizes[2]      = 12.0;
   sizes[3]      = 15.0;

   times[0] = 0.0;
   times[1] = 0.3;
   times[2] = 0.6;
   times[3] = 1.0;

   useInvAlpha = false;
};
datablock ParticleEmitterData(FireBall2ExplosionEmitter)
{
   ejectionPeriodMS = 3;
   periodVarianceMS = 0;
   ejectionVelocity = 15;
   velocityVariance = 1.0;
   ejectionOffset   = 3.0;
   thetaMin         = 0;
   thetaMax         = 40;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvance = false;
   particles = "FireBall2ExplosionParticle";
};

datablock ParticleData(FireBall2ExplosionBParticle)
{
   dragCoefficient      = 1;
   gravityCoefficient   = 0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 800;
   lifetimeVarianceMS   = 200;
   textureName          = "base/data/particles/cloud";
   spinSpeed = 10.0;
   spinRandomMin = -50.0;
   spinRandomMax = 50.0;
   colors[0]     = "0.9 0.7 0.6 0.9";
   colors[1]     = "0.9 0.3 0.0 0.9";
   colors[2]     = "0.6 0.2 0.0 0.7";
   colors[3]     = "0.6 0.1 0.0 0.0";

   sizes[0]      = 2.0;
   sizes[1]      = 3.0;
   sizes[2]      = 5.0;
   sizes[3]      = 7.0;

   times[0] = 0.0;
   times[1] = 0.3;
   times[2] = 0.6;
   times[3] = 1.0;

   useInvAlpha = false;
};
datablock ParticleEmitterData(FireBall2ExplosionBEmitter)
{
   ejectionPeriodMS = 2;
   periodVarianceMS = 0;
   ejectionVelocity = 10;
   velocityVariance = 1.0;
   ejectionOffset   = 0.0;
   thetaMin         = 0;
   thetaMax         = 40;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvance = false;
   particles = "FireBall2ExplosionBParticle";
};

datablock ParticleData(FireBall2ExplosionRingBParticle)
{
   dragCoefficient      = 4;
   gravityCoefficient   = 0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 700;
   lifetimeVarianceMS   = 100;
   textureName          = "base/data/particles/cloud";
   spinSpeed = 10.0;
   spinRandomMin = -50.0;
   spinRandomMax = 50.0;
   colors[0]     = "0.9 0.7 0.6 0.9";
   colors[1]     = "0.9 0.3 0.0 0.9";
   colors[2]     = "0.6 0.2 0.0 0.7";
   colors[3]     = "0.6 0.1 0.0 0.0";

   sizes[0]      = 1.0;
   sizes[1]      = 1.5;
   sizes[2]      = 2.0;
   sizes[3]      = 3.0;

   times[0] = 0.0;
   times[1] = 0.3;
   times[2] = 0.6;
   times[3] = 1.0;

   useInvAlpha = false;
};
datablock ParticleEmitterData(FireBall2ExplosionRingBEmitter)
{
   lifetimeMS = 60;

   ejectionPeriodMS = 1;
   periodVarianceMS = 0;
   ejectionVelocity = 20;
   velocityVariance = 1.0;
   ejectionOffset   = 0.5;
   thetaMin         = 89;
   thetaMax         = 91;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvance = false;
   particles = "FireBall2ExplosionRingBParticle";
};

datablock ParticleData(FireBall2ExplosionRingParticle)
{
   dragCoefficient      = 4;
   gravityCoefficient   = -0.0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 1000;
   lifetimeVarianceMS   = 100;
   textureName          = "base/data/particles/cloud";
   spinSpeed = 10.0;
   spinRandomMin = -500.0;
   spinRandomMax = 500.0;
   colors[0]     = "0.9 0.7 0.6 0.9";
   colors[1]     = "0.9 0.3 0.0 0.9";
   colors[2]     = "0.6 0.2 0.0 0.4";
   colors[3]     = "0.6 0.1 0.0 0.0";

   sizes[0]      = 3.0;
   sizes[1]      = 5.0;
   sizes[2]      = 6.0;
   sizes[3]      = 7.0;

   times[0] = 0.0;
   times[1] = 0.3;
   times[2] = 0.6;
   times[3] = 1.0;

   useInvAlpha = false;
};
datablock ParticleEmitterData(FireBall2ExplosionRingEmitter)
{
   lifeTimeMS = 50;

   ejectionPeriodMS = 1;
   periodVarianceMS = 0;
   ejectionVelocity = 30;
   velocityVariance = 10.0;
   ejectionOffset   = 2.0;
   thetaMin         = 80;
   thetaMax         = 90;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvance = false;
   particles = "FireBall2ExplosionRingParticle";
};


datablock ParticleData(FireBall2ExplosionChunkAParticle)
{
   dragCoefficient      = 2;
   gravityCoefficient   = 0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 800;
   lifetimeVarianceMS   = 500;
   textureName          = "base/data/particles/cloud";
   spinSpeed = 10.0;
   colors[0]     = "0.9 0.9 0.6 0.9";
   colors[1]     = "0.9 0.6 0.0 0.9";
   colors[2]     = "0.6 0.0 0.0 0.7";
   colors[3]     = "0.2 0.0 0.0 0.0";

   sizes[0]      = 2.0;
   sizes[1]      = 3.5;
   sizes[2]      = 3.0;
   sizes[3]      = 3.0;

   times[0] = 0.0;
   times[1] = 0.3;
   times[2] = 0.9;
   times[3] = 1.0;

   useInvAlpha = false;
};
datablock ParticleEmitterData(FireBall2ExplosionChunkAEmitter)
{
   lifeTimeMS = 50;

   ejectionPeriodMS = 1;
   periodVarianceMS = 0;
   ejectionVelocity = 25;
   velocityVariance = 20.0;
   ejectionOffset   = 1.0;
   thetaMin         = 0;
   thetaMax         = 180;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvance = false;
   particles = "FireBall2ExplosionChunkAParticle";
};

datablock ParticleData(FireBall2ExplosionChunkBParticle)
{
   dragCoefficient      = 3;
   gravityCoefficient   = 0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 800;
   lifetimeVarianceMS   = 500;
   textureName          = "base/data/particles/cloud";
   spinSpeed = 10.0;
   colors[0]     = "0.9 0.9 0.6 0.9";
   colors[1]     = "0.9 0.6 0.0 0.6";
   colors[2]     = "0.6 0.0 0.0 0.3";
   colors[3]     = "0.2 0.0 0.0 0.0";

   sizes[0]      = 1.0;
   sizes[1]      = 2.5;
   sizes[2]      = 4.0;
   sizes[3]      = 6.5;

   times[0] = 0.0;
   times[1] = 0.3;
   times[2] = 0.6;
   times[3] = 1.0;

   useInvAlpha = false;
};
datablock ParticleEmitterData(FireBall2ExplosionChunkBEmitter)
{
//   lifeTimeMS = 20;

   ejectionPeriodMS = 1;
   periodVarianceMS = 0;
   ejectionVelocity = 10;
   velocityVariance = 5.0;
   ejectionOffset   = 0.0;
   thetaMin         = 0;
   thetaMax         = 180;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvance = false;
   particles = "FireBall2ExplosionChunkBParticle";
};

datablock ExplosionData(FireBall2SubExplosion2)
{
   lifetimeMS  = 600; //600
   delayMS   = 400; //400
   offset    = 6.5;
   playSpeed = 1.0;
   delayVariance = 200;

   explosionscale = "0.5 0.5 0.5";

   particleEmitter = FireBall2ExplosionBEmitter;
   particleDensity = 5;
   particleRadius = 0.2;

   emitter[0] = FireBall2ExplosionChunkBEmitter;

   // Dynamic light
   lightStartRadius = 3;
   lightEndRadius = 0;
   lightStartColor = "1 1 0 1";
   lightEndColor = "1 0 0 0";

};

datablock ExplosionData(FireBall2SubExplosion1)
{
   lifetimeMS  = 400;
   delayMS   = 200; //200
   offset    = 5.0;
   playSpeed = 1.0;
   delayVariance = 200;

   explosionscale = "0.75 0.75 0.75";

   particleEmitter = FireBall2ExplosionBEmitter;
   particleDensity = 7;
   particleRadius = 0.3;

   emitter[0] = FireBall2ExplosionChunkBEmitter;
   emitter[1] = FireBall2ExplosionRingBEmitter;

   subExplosion[0] = FireBall2SubExplosion2;
   subExplosion[1] = FireBall2SubExplosion2;
   subExplosion[2] = FireBall2SubExplosion2;

   // Dynamic light
   lightStartRadius = 7;
   lightEndRadius = 0;
   lightStartColor = "1 1 0 1";
   lightEndColor = "1 0 0 0";

};

datablock ExplosionData(FireBall2LightExplosion)
{
   lifetimeMS  = 10000;
   delayMS   = 0;
   offset    = 1.0;
   playSpeed = 1.0;
   delayVariance = 000;

   // Dynamic light
   lightStartRadius = 13;
   lightEndRadius = 0;
   lightStartColor = "1 0.4 0 1";
   lightEndColor = "1 0 0 0";

};

datablock ExplosionData(FireBall2Explosion)
{
   explosionShape = "";
   soundProfile = Fireball2ExplodeSound;

   lifeTimeMS = 600; //400

   particleEmitter = FireBall2ExplosionEmitter;
   particleDensity = 10;
   particleRadius = 0.5;

   emitter[0] = FireBall2ExplosionChunkAEmitter;
   emitter[1] = FireBall2ExplosionRingEmitter;

   subExplosion[0] = FireBall2SubExplosion1;
   subExplosion[1] = FireBall2SubExplosion1;
   subExplosion[2] = FireBall2SubExplosion1;
   subExplosion[3] = FireBall2SubExplosion1;
   subExplosion[4] = FireBall2LightExplosion;

   faceViewer     = true;
   explosionScale = "1 1 1";

   shakeCamera = true;
   camShakeFreq = "10.0 11.0 10.0";
   camShakeAmp = "3.0 10.0 3.0";
   camShakeDuration = 0.5;
   camShakeRadius = 20.0;

   // Dynamic light
   lightStartRadius = 15;
   lightEndRadius = 0;
   lightStartColor = "1 1 0 1";
   lightEndColor = "1 0 0 0";

   damageRadius = 10;
   radiusDamage = 150;

   impulseRadius = 3;
   impulseForce = 4000;

   playerBurnTime = 5000;
};

/////////////////
//  Projectile //
/////////////////

datablock ParticleData(Fireball2TrailParticle)
   {
      dragCoefficient      = 3;
      gravityCoefficient   = -0.0;
      inheritedVelFactor   = 0.15;
      constantAcceleration = 0.0;
      lifetimeMS           = 1500;
      lifetimeVarianceMS   = 805;
      textureName          = "base/data/particles/cloud";
      spinSpeed = 10.0;
      spinRandomMin = -150.0;
      spinRandomMax = 150.0;
      colors[0]     = "1.0 0.9 0.8 0.6";
      colors[1]     = "1.0 0.2 0.0 0.5";
      colors[2]     = "0.9 0.2 0.0 0.3";
      colors[3]     = "0.4 0.0 0.0 0.0";

      sizes[0]      = 2.25;
      sizes[1]      = 3.55;
      sizes[2]      = 2.35;
      sizes[3]      = 1.05;

      times[0] = 0.0;
      times[1] = 0.15;
      times[2] = 0.4;
      times[3] = 1.0;

      useInvAlpha = false;
   };
datablock ParticleEmitterData(Fireball2TrailEmitter)
   {
      ejectionPeriodMS = 3;
      periodVarianceMS = 1;
      ejectionVelocity = 10.0;
      velocityVariance = 0.0;
      ejectionOffset   = 0.5;
      thetaMin         = 0;
      thetaMax         = 90;
      phiReferenceVel  = 0;
      phiVariance      = 360;
      overrideAdvance = false;
      particles = "Fireball2TrailParticle";
   };

   AddDamageType("Fireball2Direct",   '<bitmap:add-ons/Projectile_FireBall2/Fireball2> %1',    '%2 <bitmap:add-ons/Projectile_FireBall2/Fireball2> %1',1,1);
   AddDamageType("Fireball2Radius",   '<bitmap:add-ons/Projectile_FireBall2/Fireball2Radius> %1',    '%2 <bitmap:add-ons/Projectile_FireBall2/Fireball2Radius> %1',1,0);
datablock ProjectileData(FireBall2Projectile)
{
   projectileShapeName = "base/data/shapes/empty.dts";
   directDamage        = 30;
   directDamageType = $DamageType::Fireball2Direct;
   radiusDamageType = $DamageType::Fireball2Radius;
   impactImpulse    = 1000;
   verticalImpulse    = 1000;
   explosion           = FireBall2Explosion;
   particleEmitter     = Fireball2TrailEmitter;

   brickExplosionRadius = 3;
   brickExplosionImpact = false;          //destroy a brick if we hit it directly?
   brickExplosionForce  = 30;             
   brickExplosionMaxVolume = 30;          //max volume of bricks that we can destroy
   brickExplosionMaxVolumeFloating = 60;  //max volume of bricks that we can destroy if they aren't connected to the ground (should always be >= brickExplosionMaxVolume)

   sound = Fireball2LoopSound;

   muzzleVelocity      = 65;
   velInheritFactor    = 1.0;

   armingDelay         = 00;
   lifetime            = 12000;
   fadeDelay           = 11500;
   bounceElasticity    = 0.5;
   bounceFriction      = 0.20;
   isBallistic         = true;
   gravityMod = 1.0;

   hasLight    = true;
   lightRadius = 5.0;
   lightColor  = "1 0.5 0.0";

   uiName = "Fire ball";
};

« Last Edit: June 25, 2009, 11:34:43 AM by Muffinmix »

nice job muffinmix looks epic



nice!!!( now i need to learn to script , so i can make a gun that shoots fireballs)

Update, and

nice!!!( now i need to learn to script , so i can make a gun that shoots fireballs)

No one release that projectile unless I give the ok.

I wish I could make freaking particles like you. I suck at them.

hey muffin mix, i have a model that would be a perfect fireball pistol, and was wondering if you would script it to shoot fire balls? It is a modified version of the Walther p22.

I declare Muffinmix the best blockland effects artist ever.


Nice! You should release that.

Nice! You should release that.

I would, probably in the form of a fire tank or something, but I'm too busy IRL and with other important projects to model the damn thing. Plus it's a bit too big for a handheld weapon.

Not really. BobandRob made a Handheld tank, maybe you could make a mortor tube for it?

I would, probably in the form of a fire tank or something, but I'm too busy IRL and with other important projects to model the damn thing. Plus it's a bit too big for a handheld weapon.
Do you mind if I make a tank that shoots fire.