]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
changed cl_stainmaps to be a loadtime cvar which decides whether to
[xonotic/darkplaces.git] / cl_particles.c
index 55f99a59e45d9675d5efb05aa4a1fc99e90624f1..b491a0f2d12d1c1bcc3f74065f10caf0fc163df8 100644 (file)
@@ -24,6 +24,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "image.h"
 #include "r_shadow.h"
 
+#define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
+#define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
+
 // must match ptype_t values
 particletype_t particletype[pt_total] =
 {
@@ -456,7 +459,7 @@ void CL_Particles_Shutdown (void)
 // px,py,pz - starting origin of particle
 // pvx,pvy,pvz - starting velocity of particle
 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
-static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
+static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime)
 {
        int l1, l2;
        particle_t *part;
@@ -466,6 +469,8 @@ static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pc
        for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
        if (cl.free_particle >= cl.max_particles)
                return NULL;
+       if (!lifetime)
+               lifetime = palpha / min(1, palphafade);
        part = &cl.particles[cl.free_particle++];
        if (cl.num_particles < cl.free_particle)
                cl.num_particles = cl.free_particle;
@@ -476,7 +481,6 @@ static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pc
        part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
        part->color[1] = ((((pcolor1 >>  8) & 0xFF) * l1 + ((pcolor2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
        part->color[2] = ((((pcolor1 >>  0) & 0xFF) * l1 + ((pcolor2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
-       part->color[3] = 0xFF;
        part->texnum = ptex;
        part->size = psize;
        part->sizeincrease = psizeincrease;
@@ -494,8 +498,9 @@ static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pc
        part->time2 = 0;
        part->airfriction = pairfriction;
        part->liquidfriction = pliquidfriction;
-       part->die = cl.time + part->alpha / (part->alphafade ? part->alphafade : 1);
+       part->die = cl.time + lifetime;
        part->delayedcollisions = 0;
+       part->qualityreduction = pqualityreduction;
        if (part->typeindex == pt_blood)
                part->gravity += 1; // FIXME: this is a legacy hack, effectinfo.txt doesn't have gravity on blood (nor do the particle calls in the engine)
        // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
@@ -512,14 +517,14 @@ static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pc
                VectorMA(part->org, lifetime, part->vel, endvec);
                trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
                part->die = cl.time + lifetime * trace.fraction;
-               part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0);
+               part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0);
                if (part2)
                {
                        part2->delayedspawn = part->die;
                        part2->die += part->die - cl.time;
                        for (i = rand() & 7;i < 10;i++)
                        {
-                               part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32);
+                               part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0);
                                if (part2)
                                {
                                        part2->delayedspawn = part->die;
@@ -565,7 +570,6 @@ void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t
        decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
        decal->color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
        decal->color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
-       decal->color[3] = 0xFF;
        decal->owner = hitent;
        if (hitent)
        {
@@ -619,19 +623,14 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        if (count == 1024)
                                CL_ParticleExplosion(center);
                        else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
-                               CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                               CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
                        else
                        {
                                count *= cl_particles_quality.value;
                                for (;count > 0;count--)
                                {
                                        int k = particlepalette[palettecolor + (rand()&7)];
-                                       if (cl_particles_quake.integer)
-                                               CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
-                                       else if (gamemode == GAME_GOODVSBAD2)
-                                               CL_NewParticle(pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
-                                       else
-                                               CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
+                                       CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5));
                                }
                        }
                }
@@ -653,10 +652,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
                                CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
+                               CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                }
                // bullet hole
-               if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+               R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
        }
        else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
@@ -672,10 +672,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
                                CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
+                               CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                }
                // bullet hole
-               if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+               R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
                CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
@@ -692,10 +693,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
                                CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
+                               CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                }
                // bullet hole
-               if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+               R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
        }
        else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
@@ -711,10 +713,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
                                CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
+                               CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                }
                // bullet hole
-               if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+               R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
                CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
@@ -723,13 +726,14 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                if (!cl_particles_blood.integer)
                        return;
                if (cl_particles_quake.integer)
-                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
+                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
                else
                {
                        static double bloodaccumulator = 0;
+                       //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0);
                        bloodaccumulator += count * 0.333 * cl_particles_quality.value;
                        for (;bloodaccumulator > 0;bloodaccumulator--)
-                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
+                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0);
                }
        }
        else if (effectnameindex == EFFECT_TE_SPARK)
@@ -737,7 +741,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
        else if (effectnameindex == EFFECT_TE_PLASMABURN)
        {
                // plasma scorch mark
-               if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
+               R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
                CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
                CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
@@ -751,10 +755,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
                                CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
+                               CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                }
                // bullet hole
-               if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+               R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
        }
        else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
@@ -767,10 +772,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
                                CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
+                               CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                }
                // bullet hole
-               if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+               R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
                CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
@@ -792,9 +798,9 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        for (i = 0;i < 1024 * cl_particles_quality.value;i++)
                        {
                                if (i & 1)
-                                       CL_NewParticle(pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
+                                       CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0);
                                else
-                                       CL_NewParticle(pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
+                                       CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0);
                        }
                }
                else
@@ -807,7 +813,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
        {
                count *= cl_particles_quality.value;
                while (count-- > 0)
-                       CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
+                       CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0);
        }
        else if (effectnameindex == EFFECT_TE_LAVASPLASH)
        {
@@ -826,7 +832,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                org[1] = center[1] + dir[1];
                                org[2] = center[2] + lhrandom(0, 64);
                                vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
-                               CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
+                               CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62));
                        }
                }
        }
@@ -835,7 +841,10 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                float i, j, k, inc, vel;
                vec3_t dir;
 
-               inc = 8 / cl_particles_quality.value;
+               if (cl_particles_quake.integer)
+                       inc = 4 / cl_particles_quality.value;
+               else
+                       inc = 8 / cl_particles_quality.value;
                for (i = -16;i < 16;i += inc)
                {
                        for (j = -16;j < 16;j += inc)
@@ -845,22 +854,26 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        VectorSet(dir, i*8, j*8, k*8);
                                        VectorNormalize(dir);
                                        vel = lhrandom(50, 113);
-                                       CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
+                                       if (cl_particles_quake.integer)
+                                               CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34));
+                                       else
+                                               CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0);
                                }
                        }
                }
-               CL_NewParticle(pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
+               if (!cl_particles_quake.integer)
+                       CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0);
                CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_TEI_G3)
-               CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
+               CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0);
        else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
        {
                if (cl_particles_smoke.integer)
                {
                        count *= 0.25f * cl_particles_quality.value;
                        while (count-- > 0)
-                               CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
+                               CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0);
                }
        }
        else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
@@ -871,29 +884,28 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
        else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
        {
                float f;
-               if (cl_stainmaps.integer)
-                       R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
+               R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
                CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
                if (cl_particles_smoke.integer)
                        for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
-                               CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
+                               CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0);
                if (cl_particles_sparks.integer)
                        for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
-                               CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
+                               CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0);
                CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_EF_FLAME)
        {
                count *= 300 * cl_particles_quality.value;
                while (count-- > 0)
-                       CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
+                       CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0);
                CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_EF_STARDUST)
        {
                count *= 200 * cl_particles_quality.value;
                while (count-- > 0)
-                       CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
+                       CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0);
                CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
@@ -967,12 +979,12 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        if (cl_particles_quake.integer)
                                        {
                                                color = particlepalette[67 + (rand()&3)];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2);
                                        }
                                        else
                                        {
                                                dec = 16;
-                                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
+                                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
@@ -981,12 +993,12 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                dec = 6;
                                                color = particlepalette[67 + (rand()&3)];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2);
                                        }
                                        else
                                        {
                                                dec = 32;
-                                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
+                                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0);
                                        }
                                }
                        }
@@ -998,12 +1010,12 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                r = rand()&3;
                                                color = particlepalette[ramp3[r]];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r));
                                        }
                                        else
                                        {
-                                               CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
-                                               CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20);
+                                               CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
+                                               CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_GRENADE)
@@ -1012,11 +1024,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                r = 2 + (rand()%5);
                                                color = particlepalette[ramp3[r]];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r));
                                        }
                                        else
                                        {
-                                               CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_WIZSPIKE)
@@ -1025,18 +1037,18 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                dec = 6;
                                                color = particlepalette[52 + (rand()&7)];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5);
                                        }
                                        else if (gamemode == GAME_GOODVSBAD2)
                                        {
                                                dec = 6;
-                                               CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                        }
                                        else
                                        {
                                                color = particlepalette[20 + (rand()&7)];
-                                               CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
@@ -1045,13 +1057,13 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                dec = 6;
                                                color = particlepalette[230 + (rand()&7)];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5);
                                        }
                                        else
                                        {
                                                color = particlepalette[226 + (rand()&7)];
-                                               CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_VORESPIKE)
@@ -1059,40 +1071,40 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        if (cl_particles_quake.integer)
                                        {
                                                color = particlepalette[152 + (rand()&3)];
-                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
+                                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3);
                                        }
                                        else if (gamemode == GAME_GOODVSBAD2)
                                        {
                                                dec = 6;
-                                               CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                        }
                                        else if (gamemode == GAME_PRYDON)
                                        {
                                                dec = 6;
-                                               CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                        }
                                        else
-                                               CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                                }
                                else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
                                {
                                        dec = 7;
-                                       CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
+                                       CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0);
                                }
                                else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
                                {
                                        dec = 4;
-                                       CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
+                                       CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0);
                                }
                                else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
-                                       CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                       CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
                        }
                        if (bubbles)
                        {
                                if (effectnameindex == EFFECT_TR_ROCKET)
-                                       CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
+                                       CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0);
                                else if (effectnameindex == EFFECT_TR_GRENADE)
-                                       CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
+                                       CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0);
                        }
                        // advance to next time and position
                        dec *= qd;
@@ -1114,10 +1126,11 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
 {
        vec3_t center;
        qboolean found = false;
-       if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
-               return; // invalid effect index
-       if (!particleeffectname[effectnameindex][0])
+       if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
+       {
+               Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
                return; // no such effect
+       }
        VectorLerp(originmins, 0.5, originmaxs, center);
        if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
        {
@@ -1187,7 +1200,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                if (info->particletype == pt_decal)
                                        CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
                                else if (info->particletype == pt_beam)
-                                       CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
+                                       CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0);
                                else
                                {
                                        if (!cl_particles.integer)
@@ -1228,7 +1241,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                                        trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
                                                }
                                                VectorRandom(rvec);
-                                               CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0);
+                                               CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, 0);
                                                if (trailstep)
                                                        VectorMA(trailpos, trailstep, traildir, trailpos);
                                        }
@@ -1271,7 +1284,7 @@ void CL_EntityParticles (const entity_t *ent)
                v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
                v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
                v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
-               CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0);
+               CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0);
        }
 }
 
@@ -1323,16 +1336,16 @@ void CL_ReadPointFile_f (void)
                if (cl.num_particles < cl.max_particles - 3)
                {
                        s++;
-                       CL_NewParticle(pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0);
+                       CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30);
                }
        }
        Mem_Free(pointfile);
        VectorCopy(leakorg, org);
        Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
 
-       CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0);
-       CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0);
-       CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0);
+       CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30);
+       CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30);
+       CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30);
 }
 
 /*
@@ -1349,7 +1362,7 @@ void CL_ParseParticleEffect (void)
 
        MSG_ReadVector(org, cls.protocol);
        for (i=0 ; i<3 ; i++)
-               dir[i] = MSG_ReadChar ();
+               dir[i] = MSG_ReadChar () * (1.0 / 16.0);
        msgcount = MSG_ReadByte ();
        color = MSG_ReadByte ();
 
@@ -1373,8 +1386,7 @@ void CL_ParticleExplosion (const vec3_t org)
        trace_t trace;
        //vec3_t v;
        //vec3_t v2;
-       if (cl_stainmaps.integer)
-               R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
+       R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
        CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
 
        if (cl_particles_quake.integer)
@@ -1386,12 +1398,12 @@ void CL_ParticleExplosion (const vec3_t org)
                        if (i & 1)
                        {
                                color = particlepalette[ramp1[r]];
-                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
+                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r));
                        }
                        else
                        {
                                color = particlepalette[ramp2[r]];
-                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
+                               CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r));
                        }
                }
        }
@@ -1402,7 +1414,7 @@ void CL_ParticleExplosion (const vec3_t org)
                {
                        if (cl_particles.integer && cl_particles_bubbles.integer)
                                for (i = 0;i < 128 * cl_particles_quality.value;i++)
-                                       CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96);
+                                       CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0);
                }
                else
                {
@@ -1422,7 +1434,7 @@ void CL_ParticleExplosion (const vec3_t org)
                                        }
                                        VectorSubtract(trace.endpos, org, v2);
                                        VectorScale(v2, 2.0f, v2);
-                                       CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
+                                       CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0);
                                }
                        }
                }
@@ -1447,9 +1459,9 @@ void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
        {
                k = particlepalette[colorStart + (i % colorLength)];
                if (cl_particles_quake.integer)
-                       CL_NewParticle(pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
+                       CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3);
                else
-                       CL_NewParticle(pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192);
+                       CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0);
        }
 }
 
@@ -1459,7 +1471,7 @@ static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const ve
        {
                sparkcount *= cl_particles_quality.value;
                while(sparkcount-- > 0)
-                       CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64);
+                       CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0);
        }
 }
 
@@ -1469,7 +1481,7 @@ static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec
        {
                smokecount *= cl_particles_quality.value;
                while(smokecount-- > 0)
-                       CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0);
+                       CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0);
        }
 }
 
@@ -1482,24 +1494,29 @@ void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
        while (count--)
        {
                k = particlepalette[colorbase + (rand()&3)];
-               CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel);
+               CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0);
        }
 }
 
 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
 {
        int k;
-       float z, minz, maxz;
+       float minz, maxz, lifetime = 30;
        if (!cl_particles.integer) return;
        if (dir[2] < 0) // falling
-               z = maxs[2];
+       {
+               minz = maxs[2] + dir[2] * 0.1;
+               maxz = maxs[2];
+               if (cl.worldmodel)
+                       lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
+       }
        else // rising??
-               z = mins[2];
-
-       minz = z - fabs(dir[2]) * 0.1;
-       maxz = z + fabs(dir[2]) * 0.1;
-       minz = bound(mins[2], minz, maxs[2]);
-       maxz = bound(mins[2], maxz, maxs[2]);
+       {
+               minz = mins[2];
+               maxz = maxs[2] + dir[2] * 0.1;
+               if (cl.worldmodel)
+                       lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
+       }
 
        count = (int)(count * cl_particles_quality.value);
 
@@ -1513,9 +1530,9 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
                {
                        k = particlepalette[colorbase + (rand()&3)];
                        if (gamemode == GAME_GOODVSBAD2)
-                               CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
+                               CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime);
                        else
-                               CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
+                               CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime);
                }
                break;
        case 1:
@@ -1524,9 +1541,9 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
                {
                        k = particlepalette[colorbase + (rand()&3)];
                        if (gamemode == GAME_GOODVSBAD2)
-                               CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
+                               CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime);
                        else
-                               CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
+                               CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime);
                }
                break;
        default:
@@ -1548,7 +1565,9 @@ static rtexture_t *particlefonttexture;
 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
 
 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
+static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
+static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
 
 #define PARTICLETEXTURESIZE 64
 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
@@ -1885,26 +1904,29 @@ static void r_part_shutdown(void)
 
 static void r_part_newmap(void)
 {
+       CL_Particles_LoadEffectInfo();
 }
 
 #define BATCHSIZE 256
-int particle_element3i[BATCHSIZE*6];
+unsigned short particle_elements[BATCHSIZE*6];
 
 void R_Particles_Init (void)
 {
        int i;
        for (i = 0;i < BATCHSIZE;i++)
        {
-               particle_element3i[i*6+0] = i*4+0;
-               particle_element3i[i*6+1] = i*4+1;
-               particle_element3i[i*6+2] = i*4+2;
-               particle_element3i[i*6+3] = i*4+0;
-               particle_element3i[i*6+4] = i*4+2;
-               particle_element3i[i*6+5] = i*4+3;
+               particle_elements[i*6+0] = i*4+0;
+               particle_elements[i*6+1] = i*4+1;
+               particle_elements[i*6+2] = i*4+2;
+               particle_elements[i*6+3] = i*4+0;
+               particle_elements[i*6+4] = i*4+2;
+               particle_elements[i*6+5] = i*4+3;
        }
 
        Cvar_RegisterVariable(&r_drawparticles);
+       Cvar_RegisterVariable(&r_drawparticles_drawdistance);
        Cvar_RegisterVariable(&r_drawdecals);
+       Cvar_RegisterVariable(&r_drawdecals_drawdistance);
        R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
 }
 
@@ -1924,6 +1946,7 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
        R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
        R_Mesh_ColorPointer(particle_color4f, 0, 0);
+       R_SetupGenericShader(true);
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
        GL_PolygonOffset(0, 0);
@@ -1978,7 +2001,7 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
        R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
        GL_LockArrays(0, numsurfaces*4);
-       R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
+       R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
        GL_LockArrays(0, 0);
 }
 
@@ -1988,15 +2011,18 @@ void R_DrawDecals (void)
        decal_t *decal;
        float frametime;
        float decalfade;
+       float drawdist2;
 
        frametime = bound(0, cl.time - cl.decals_updatetime, 1);
-       cl.decals_updatetime += frametime;
+       cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
 
        // LordHavoc: early out conditions
        if ((!cl.num_decals) || (!r_drawdecals.integer))
                return;
 
        decalfade = frametime * 256 / cl_decals_fadetime.value;
+       drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
+       drawdist2 = drawdist2*drawdist2;
 
        for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
        {
@@ -2020,7 +2046,9 @@ void R_DrawDecals (void)
                        else
                                goto killdecal;
                }
-               R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
+
+               if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
+                       R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
                continue;
 killdecal:
                decal->typeindex = 0;
@@ -2031,6 +2059,15 @@ killdecal:
        // reduce cl.num_decals if possible
        while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
                cl.num_decals--;
+
+       if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
+       {
+               decal_t *olddecals = cl.decals;
+               cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
+               cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
+               memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
+               Mem_Free(olddecals);
+       }
 }
 
 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
@@ -2055,6 +2092,7 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
        R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
        R_Mesh_ColorPointer(particle_color4f, 0, 0);
+       R_SetupGenericShader(true);
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
        GL_PolygonOffset(0, 0);
@@ -2156,8 +2194,8 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        t2f[6] = tex->s2;t2f[7] = tex->t2;
                        break;
                case PARTICLE_SPARK:
-                       VectorMA(p->org, -0.02, p->vel, v);
-                       VectorMA(p->org, 0.02, p->vel, up2);
+                       VectorMA(p->org, -0.04, p->vel, v);
+                       VectorMA(p->org, 0.04, p->vel, up2);
                        R_CalcBeam_Vertex3f(v3f, v, up2, size);
                        t2f[0] = tex->s1;t2f[1] = tex->t2;
                        t2f[2] = tex->s1;t2f[3] = tex->t1;
@@ -2220,23 +2258,24 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                }
 
                batchcount = surfacelistindex - batchstart;
-               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
+               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
        }
        GL_LockArrays(0, 0);
 }
 
 void R_DrawParticles (void)
 {
-       int i, j, a, content;
+       int i, a, content;
        float minparticledist;
        particle_t *p;
        float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
+       float drawdist2;
        int hitent;
        trace_t trace;
        qboolean update;
 
        frametime = bound(0, cl.time - cl.particles_updatetime, 1);
-       cl.particles_updatetime += frametime;
+       cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
 
        // LordHavoc: early out conditions
        if ((!cl.num_particles) || (!r_drawparticles.integer))
@@ -2247,12 +2286,17 @@ void R_DrawParticles (void)
        dvel = 1+4*frametime;
        decalfade = frametime * 255 / cl_decals_fadetime.value;
        update = frametime > 0;
+       drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
+       drawdist2 = drawdist2*drawdist2;
 
-       j = 0;
        for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
        {
                if (!p->typeindex)
+               {
+                       if (cl.free_particle > i)
+                               cl.free_particle = i;
                        continue;
+               }
 
                if (update)
                {
@@ -2304,35 +2348,12 @@ void R_DrawParticles (void)
                                        if (trace.fraction < 1)
                                        {
                                                VectorCopy(trace.endpos, p->org);
-                                               if (p->typeindex == pt_rain)
-                                               {
-                                                       // raindrop - splash on solid/water/slime/lava
-                                                       int count;
-                                                       // convert from a raindrop particle to a rainsplash decal
-                                                       VectorCopy(trace.plane.normal, p->vel);
-                                                       VectorAdd(p->org, p->vel, p->org);
-                                                       p->typeindex = pt_raindecal;
-                                                       p->texnum = tex_rainsplash;
-                                                       p->time2 = cl.time;
-                                                       p->alphafade = p->alpha / 0.4;
-                                                       p->bounce = 0;
-                                                       p->airfriction = 0;
-                                                       p->liquidfriction = 0;
-                                                       p->gravity = 0;
-                                                       p->size *= 1.0f;
-                                                       p->sizeincrease = p->size * 20;
-                                                       count = (int)lhrandom(1, 10);
-                                                       while(count--)
-                                                               CL_NewParticle(pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
-                                                       continue;
-                                               }
-                                               else if (p->typeindex == pt_blood)
+                                               if (p->typeindex == pt_blood)
                                                {
                                                        // blood - splash on solid
                                                        if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
                                                                goto killparticle;
-                                                       if (cl_stainmaps.integer)
-                                                               R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
+                                                       R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
                                                        if (cl_decals.integer)
                                                        {
                                                                // create a decal for the blood splat
@@ -2406,8 +2427,18 @@ void R_DrawParticles (void)
                // don't render particles too close to the view (they chew fillrate)
                // also don't render particles behind the view (useless)
                // further checks to cull to the frustum would be too slow here
-               if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM)
+               switch(p->typeindex)
+               {
+               case pt_beam:
+                       // beams have no culling
                        R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
+                       break;
+               default:
+                       // anything else just has to be in front of the viewer and visible at this distance
+                       if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
+                               R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
+                       break;
+               }
 
                continue;
 killparticle:
@@ -2419,4 +2450,13 @@ killparticle:
        // reduce cl.num_particles if possible
        while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
                cl.num_particles--;
+
+       if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
+       {
+               particle_t *oldparticles = cl.particles;
+               cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
+               cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
+               memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
+               Mem_Free(oldparticles);
+       }
 }