Detailed Particle Behavoir Reference

Discussion in 'Resources' started by RingOfStorms, Jan 7, 2015.

Not open for further replies.
1. Offline

RingOfStorms

Hello guys, I'm making this post as a resource for people who have made particle libraries, or just want to learn about some of the more nitty-gritty details of how the client handles particles sent from the server. This is a very dense read and I attempted to make useful titles so you can easily skip through to the section you're interested in.

Do note, I may or may not update this as minecraft is updates, but do check back or ask when a new update comes out.

For the length of this tutorial I will be refering to the different parts of the particle packet. I'm following the names that are shown here on the protocol wiki. The only different is that I will be using the word speed instead of particle data.

To start out with, I'll be going over the first step that the client does when it reads a new particle packet. After that I will be listing out the specifics of every single particle and how they react to the two different packet handling methods I describe below.

Under each section I've included a spoiler that includes the NMS code that is relevant to the explanation, this will help some people understand my explanations a bit better. I deobfuscated some parts of the code snippets, but a lot will still be obfuscated.

Particle Packet Handling on the client

The very first check that occurs checks if the particle count is zero, or not. These two outcomes produce different effects and spawn the particles slightly different depending on the particle in question.

When the particle count is equal to zero:
Three new x/y/z variables are created by multiplying the offsetX/Y/Z by the particle's speed. These newly created x/y/z will be passed in instead of the offsets that were originally in the packet.

In a lot of the cases when the zero particle count is used then the newly created x/y/z represent the direction of the particle, however it is not quite the same for every single particle.

Code:java
` double var2 = (double)(packetIn.particleSpeedData() * packetIn.offsetX());double var4 = (double)(packetIn.particleSpeedData() * packetIn.offsetY());double var6 = (double)(packetIn.particleSpeedData() * packetIn.offsetZ()); try {    this.clientWorldController.spawnParticle(packetIn.particleType(), packetIn.ignoreRenderDistance(), packetIn.positionX(), packetIn.positionY(), packetIn.positionZ(), var2, var4, var6, packetIn.dataArray());}catch (Throwable var17){    logger.warn("Could not spawn particle effect " + packetIn.particleType());} `

When the particle count is not equal to zero:
A particle is spawned Z amount of times where Z is the particle count.
On each iteration six new variables are created.

The first three of these variables are based on the offsetX/Y/Z variables from the packet, these are the radius from the center in each axis that allows you to spawn several particles within a circular region. These bounds are then calculated using Random.nextGaussian() multiplied by each offset part. These three new x/y/z parts are then added to the original x/y/z location that was given, thus producing an effect where the number of particles are spawned inside of a circular area.

The next three variables created are the randomized motion. These are calculated by multiplying Random.nextGaussian by the particle's speed. Note: some particles have hardcoded motion and will ignore the speed inside of the packet.

Code:java
` for (int var18 = 0; var18 < packetIn.particleCount(); ++var18) {    double var3 = this.avRandomizer.nextGaussian() * (double)packetIn.offsetX();    double var5 = this.avRandomizer.nextGaussian() * (double)packetIn.offsetY();    double var7 = this.avRandomizer.nextGaussian() * (double)packetIn.offsetZ();    double var9 = this.avRandomizer.nextGaussian() * (double)packetIn.particleSpeedData();    double var11 = this.avRandomizer.nextGaussian() * (double)packetIn.particleSpeedData();    double var13 = this.avRandomizer.nextGaussian() * (double)packetIn.particleSpeedData();     try {        this.clientWorldController.spawnParticle(packetIn.particleType(), packetIn.ignoreRenderDistance(), packetIn.positionX() + var3, packetIn.positionY() + var5, packetIn.positionZ() + var7, var9, var11, var13, packetIn.dataArray());    }catch (Throwable var16){        logger.warn("Could not spawn particle effect " + packetIn.particleType());        return;    }} `

Creation of the Particle Object on the client

Before we get down to the details on each particle, I want to explain what happens between the last step of handling the packet, and the actual creation of the particle.

All of the parameters that were generated in one of the previous two handling methods are passed into the RenderGlobal instance where it does a few more things before actually making a particle.

If the particle ignores render distance (whether that is it's default nature like explosion, or you set it as true in the packet) it skips all particle settings and goes straight to spawning it in.

However, if ignore render distance is false, then it checks the particle settings. From my understanding there are three integers that represent the particle setting in the client:
All = 0
Decreased = 1
Minimal = 2

No matter what setting, if the particle is a distance greater than 16, the particle will not be created. If it is under or equal 16 then it will continue to the particle settings check...

When the client has the particles set to ...
All: The particle is created
Decreased: 33% chance that the particle is not created
Minimal: Particle is not created

Code:java
` int var16 = this.mc.gameSettings.particleSetting; if (var16 == 1 && this.theWorld.rand.nextInt(3) == 0) {    var16 = 2;} double var17 = this.mc.func_175606_aa().posX - posX;double var19 = this.mc.func_175606_aa().posY - posY;double var21 = this.mc.func_175606_aa().posZ - posZ; if (ignoreRenderDist) {    return this.mc.effectRenderer.func_178927_a(id, posX, posY, posZ, extraX, extraY, extraZ, dataArray);}else{    double var23 = 16.0D;    return var17 * var17 + var19 * var19 + var21 * var21 > 256.0D ? null : (var16 > 1 ? null : this.mc.effectRenderer.func_178927_a(id, posX, posY, posZ, extraX, extraY, extraZ, dataArray));} `

Displaying of the created particle

In the last section the particle was sent to be created, at this point the client gets the matching particle creation factory based on the particle type id that was passed in the first parameter. At this point particle's behavior will differ between some of the particles, although a large portion of them will be similar.

Here is the most updated list from the last time this post was edited of the particles in the client.
Gist

Code:java
` /*(String name of the particle, id of particle, ignores render distance by default)*/ EXPLOSION_NORMAL("explode", 0, true),EXPLOSION_LARGE("largeexplode", 1, true),EXPLOSION_HUGE("hugeexplosion", 2, true),FIREWORKS_SPARK("fireworksSpark", 3, false),WATER_BUBBLE("bubble", 4, false),WATER_SPLASH("splash", 5, false),WATER_WAKE("wake", 6, false),SUSPENDED("suspended", 7, false),SUSPENDED_DEPTH("depthsuspend", 8, false),CRIT("crit", 9, false),CRIT_MAGIC("magicCrit", 10, false),SMOKE_NORMAL("smoke", 11, false),SMOKE_LARGE("largesmoke", 12, false),SPELL("spell", 13, false),SPELL_INSTANT("instantSpell", 14, false),SPELL_MOB("mobSpell", 15, false),SPELL_MOB_AMBIENT("mobSpellAmbient", 16, false),SPELL_WITCH("witchMagic", 17, false),DRIP_WATER("dripWater", 18, false),DRIP_LAVA("dripLava", 19, false),VILLAGER_ANGRY("angryVillager", 20, false),VILLAGER_HAPPY("happyVillager", 21, false),TOWN_AURA("townaura", 22, false),NOTE("note", 23, false),PORTAL("portal", 24, false),ENCHANTMENT_TABLE("enchantmenttable", 25, false),FLAME("flame", 26, false),LAVA("lava", 27, false),FOOTSTEP("footstep", 28, false),CLOUD("cloud", 29, false),REDSTONE("reddust", 30, false),SNOWBALL("snowballpoof", 31, false),SNOW_SHOVEL("snowshovel", 32, false),SLIME("slime", 33, false),HEART("heart", 34, false),BARRIER("barrier", 35, false),ITEM_CRACK("iconcrack_", 36, false, 2),BLOCK_CRACK("blockcrack_", 37, false, 1),BLOCK_DUST("blockdust_", 38, false, 1),WATER_DROP("droplet", 39, false),ITEM_TAKE("take", 40, false),MOB_APPEARANCE("mobappearance", 41, true); `

Below this is every single particle's details and behavior, along with how each part of the particle packet effects the particle's behavior.

Each particle will contain a spoiler with what parameters from the packet effect it's behavoir. They will follow this general format:

Always:
Position X, Y, and Z

When count is 0:
blabla

When count is > 0:
blabla

NOTE: All R/G/B values are a float value between 0 and 1. They are then later multiplied by 255 when the particle is rendered, so treat all R/G/B in the particle reference as the percentage of R/G/B of the final RGB color.

Particle Reference

Barrier

Always:
Position X, Y, and Z

Town Aura

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Happy Villager

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Block Dust

Always:
Position X, Y, and Z
Block Id (Index 0 of data array) [required]

When count is 0:
Motion X, Y, and Z

Icon Crack

Always:
Position X, Y, and Z
Item Id (Index 0 of data array) [required]
Item Durability (Index 1 of data array) [optional|default:0]

When count is 0:
Motion X, Y, and Z

Slime

Always:
Position X, Y, and Z

Snowball Poof

Always:
Position X, Y, and Z

Bubble

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Cloud

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Crit

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Magic Crit

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Smoke Large

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Block Crack

Always:
Position X, Y, and Z
Block Id (Index 0 of data array) [required]

When count is 0:
Motion X, Y, and Z

Drip Lava

Always:
Position X, Y, and Z

Drip Water

Always:
Position X, Y, and Z

Enchantment Table

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z
- Note: The particle's position is shifted in the x/y/z of the vector given which gives the particle its inward movement.

Explode

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Fireworks Spark

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Wake

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Flame

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Footstep

Always:
Position X, Y, and Z

Heart

Always:
Position X, Y, and Z

Huge Explosion

Always:
Position X, Y, and Z

Large Explode

Always:
Position X, Y, and Z

Lava

Always:
Position X, Y, and Z

Note

Always:
Position X, Y, and Z

When count is 0:
Color, however the note color is fairly limited/weird, it only uses Offset X and discards Y and Z offsets
The RGB is calculated as follows:
R = MathHelper.sin(((float)offsetX+ 0.0F) * (float)Math.PI * 2.0F) * 0.65F + 0.35F;
G = MathHelper.sin(((float)offsetX+ 0.33333334F) * (float)Math.PI * 2.0F) * 0.65F + 0.35F;
B = MathHelper.sin(((float)offsetX+ 0.6666667F) * (float)Math.PI * 2.0F) * 0.65F + 0.35F;

I'm not sure if this is something mojang made up or if it is some standard color format into RGB, if you research it and find out comment below so I can add details here!

Portal

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z
- Note: The particle's position is shifted in the x/y/z of the vector given which gives the particle its inward motion.

Droplet (Water Drop)

Always:
Position X, Y, and Z

Red Dust

Always:
Position X, Y, and Z

When count is 0:
RGB color of red dust
When the offsetX is equal to 0, it will be set to 1
the rgb does get slightly randomized, so I'll include the calculations for them
flaot var12 = (float)Math.random() * 0.4F + 0.6F;
R = ((float)(Math.random() * 0.20000000298023224D) + 0.8F) * offsetX * var12;
G = ((float)(Math.random() * 0.20000000298023224D) + 0.8F) * OffsetY * var12;
B = ((float)(Math.random() * 0.20000000298023224D) + 0.8F) * OffsetZ * var12;

Smoke

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Snow Shovel

Always:
Position X, Y, and Z

When count is 0:
Motion X, Y, and Z

Splash

Always:
Position X, Y, and Z

When count is 0:
Motion X and Z
Splash motion in the Y direction is locked, and there are some requirements to set the X and the Z motion
For the motion to work the following conditions must be met:
-offsetY must equal 0
-offsetX and offsetZ must not equal 0

If those two conditions are met then the motion int the X and Z axis are set to the corresponding offsetX/Z and the motion in the Y direction is set to 0.1.

Suspend

Always:
Position X, Y, and Z

Mob Appearance

Always:
Position X, Y, and Z [specific location doesn't seem to matter on this particle effect]

Spell

Always:
Position X, Y, and Z

When count is 0:
Motion Y
motion in the X and Z directions are limited, but there are some weird special things you can do
OffsetY is used for motion Y, while motion X and Z are random between -.5 to .5

However, if the following conditions are met, the X and Z motions are multiplied by aprox .1, making it basically no movement:
- offsetX must equal 0
- offsetZ must equal 0

If either of them do not equal 0, then that X/Z motion damper is not applied.

Mob Spell Ambient

Always:
Position X, Y, and Z

When count is 0:
The offsetX/Y/Z will do the same thing as explained in the Spell particle above, but they also set color.

R/G/B is set to the offsetX/Y/Z accordingly. Because the offset values are used in two places, you can not have a special color and have the X/Z dampening at the same time. Similarly, if you have a large green value, the particle will have a larger y velocity.

Instant Spell

Always:
Position X, Y, and Z

When count is 0:
The offsetX/Y/Z will do the same thing as explained in the Spell particle above.

Mob Spell

Always:
Position X, Y, and Z

When count is 0:
The offsetX/Y/Z will do the same thing as explained in the Spell particle above, but they also set color.

R/G/B is set to the offsetX/Y/Z accordingly. Because the offset values are used in two places, you can not have a special color and have the X/Z dampening at the same time. Similarly, if you have a large green value, the particle will have a larger y velocity.

Witch Magic

Always:
Position X, Y, and Z

When count is 0:
The offsetX/Y/Z will do the same thing as explained in the Spell particle above.

That's it for now.

I have not been able to fully test every single one of these myself yet, so if there are any mistakes or things left out, please do tell me below in a reply.

#1
macmc, GrandmaJam, NathanWolf and 9 others like this.
2. Offline

Cybermaxke

You can also resize the LargeExplode particle
Instead of the offsetX you will need to the size calculated with the formula: (not tested)
offsetX = (-size * 2f) + 2f;
The value scales between 0 and infinite

And about the note particle, for the color is the actual note. Like you would use them for the note blocks, so they will scale between 0 and 24:
offsetX = note / 24f;

#2
3. Offline

NathanWolf

What a great resource, thank you for putting this together!

So, if I'm reading this correctly- of the 3 particle types that are colorizeable, there isn't one that can really do *all* colors and also stay in place?

Meaning, for the spell particles - you can't use motion dampening without also affecting the color range (the slower the particles, the darker they'll be?)

And for the red dust particle, they kind of stay in place no matter what- but you can't make colors without any red component? (I wonder how low you can make R before it decides 0? Can I pass 0.0000001?)

Well anyway this is just great, I've been wishing for colored particles for so long, I had no idea we already had them!

#3
4. Offline

ChipDev

*But, changing the amount to '1' allows offsets actually.

#4
5. Offline

RingOfStorms

Reddust stays still and can be any color.

#5
NathanWolf and ChipDev like this.
6. Offline

Skionz

@RingOfStorms I read this a few days ago and this is a wonderful thread to reference. I love particles and this will be very useful.

#6
ChipDev likes this.