implemented framerate-dependent particle quality reduction to try to
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 25 Jan 2008 08:48:57 +0000 (08:48 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Fri, 25 Jan 2008 08:48:57 +0000 (08:48 +0000)
smooth out fps swings in Ruiner in Nexuiz
added r_drawparticles_drawdistance (and one for decals)
particles and decals arrays now grow dynamically and have smaller
defaults than before
fixed bug with rain particles that made them live for about 4 minutes if
they fell out of the map

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8006 d7cf8633-e32d-0410-b094-e92efae38249

cl_main.c
cl_particles.c
cl_screen.c
client.h
gl_rmain.c
host.c
menu.c

index c5f31ff..336d288 100644 (file)
--- a/cl_main.c
+++ b/cl_main.c
@@ -89,10 +89,6 @@ cvar_t cl_locs_show = {0, "locs_show", "0", "shows defined locations for editing
 client_static_t        cls;
 client_state_t cl;
 
-#define MAX_PARTICLES                  32768   // default max # of particles at one time
-#define MAX_DECALS                             32768   // default max # of decals at one time
-#define ABSOLUTE_MIN_PARTICLES 512             // no fewer than this no matter what's on the command line
-
 /*
 =====================
 CL_ClearState
@@ -134,19 +130,10 @@ void CL_ClearState(void)
        cl.max_dlights = MAX_DLIGHTS;
        cl.max_lightstyle = MAX_LIGHTSTYLES;
        cl.max_brushmodel_entities = MAX_EDICTS;
-       cl.max_particles = MAX_PARTICLES;
-       cl.max_decals = MAX_DECALS;
+       cl.max_particles = 8192; // grows dynamically
+       cl.max_decals = 2048; // grows dynamically
        cl.max_showlmps = 0;
 
-// COMMANDLINEOPTION: Client: -particles <number> changes maximum number of particles at once, default 32768
-       i = COM_CheckParm ("-particles");
-       if (i && i < com_argc - 1)
-       {
-               cl.max_particles = (int)(atoi(com_argv[i+1]));
-               if (cl.max_particles < ABSOLUTE_MIN_PARTICLES)
-                       cl.max_particles = ABSOLUTE_MIN_PARTICLES;
-       }
-
        cl.num_dlights = 0;
        cl.num_effects = 0;
        cl.num_beams = 0;
@@ -1766,6 +1753,7 @@ void CL_UpdateWorld(void)
        r_refdef.scene.numentities = 0;
        r_refdef.scene.numlights = 0;
        r_refdef.view.matrix = identitymatrix;
+       r_refdef.view.qualityreduction = 0;
 
        cl.num_brushmodel_entities = 0;
 
@@ -1866,6 +1854,7 @@ static void CL_TimeRefresh_f (void)
        for (i = 0;i < 128;i++)
        {
                Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, i / 128.0 * 360.0, 0, 1);
+               r_refdef.view.qualityreduction = 0;
                CL_UpdateScreen();
        }
        timedelta = Sys_DoubleTime() - timestart;
index 55f99a5..74925fe 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)
        {
@@ -627,11 +631,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                {
                                        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);
+                                               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, true, 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);
+                                               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, true, 0);
                                        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, 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, true, 0);
                                }
                        }
                }
@@ -729,7 +733,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        static double bloodaccumulator = 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)
@@ -792,9 +796,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_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, true, 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_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, true, 0);
                        }
                }
                else
@@ -807,7 +811,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 +830,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, 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, true, 0);
                        }
                }
        }
@@ -845,22 +849,22 @@ 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);
+                                       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);
+               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, 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)
@@ -876,24 +880,24 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                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 +971,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, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
                                        }
                                        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 +985,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, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
                                        }
                                        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 +1002,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, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
                                        }
                                        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 +1016,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, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
                                        }
                                        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 +1029,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, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 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, true, 0);
                                        }
                                        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 +1049,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, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 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, true, 0);
                                        }
                                        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 +1063,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, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0);
                                        }
                                        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(64, 255), 256, -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(64, 255), 256, -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;
@@ -1187,7 +1191,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 +1232,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 +1275,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 +1327,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_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, 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);
 }
 
 /*
@@ -1386,12 +1390,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, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0);
                        }
                        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, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0);
                        }
                }
        }
@@ -1402,7 +1406,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 +1426,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 +1451,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_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256, true, 0);
                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_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, true, 0);
        }
 }
 
@@ -1459,7 +1463,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 +1473,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 +1486,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 +1522,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 +1533,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 +1557,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)
@@ -1904,7 +1915,9 @@ void R_Particles_Init (void)
        }
 
        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);
 }
 
@@ -1988,6 +2001,10 @@ void R_DrawDecals (void)
        decal_t *decal;
        float frametime;
        float decalfade;
+       // used as if (i & qualitymask) to skip some less important particles
+       // according to cl_minfps
+       int qualitymask = (1 << r_refdef.view.qualityreduction) - 1;
+       float drawdist2 = r_drawdecals_drawdistance.value * r_drawdecals_drawdistance.value;
 
        frametime = bound(0, cl.time - cl.decals_updatetime, 1);
        cl.decals_updatetime += frametime;
@@ -2020,7 +2037,13 @@ void R_DrawDecals (void)
                        else
                                goto killdecal;
                }
-               R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
+
+               // skip some of the less important decals according to cl_minfps
+               if (i & qualitymask)
+                       continue;
+
+               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 +2054,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)
@@ -2156,8 +2188,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;
@@ -2227,13 +2259,17 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
 
 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 = r_drawparticles_drawdistance.value * r_drawparticles_drawdistance.value;
        int hitent;
        trace_t trace;
        qboolean update;
+       // used as if (i & qualitymask) to skip some less important particles
+       // according to cl_minfps
+       int qualitymask = (1 << r_refdef.view.qualityreduction) - 1;
 
        frametime = bound(0, cl.time - cl.particles_updatetime, 1);
        cl.particles_updatetime += frametime;
@@ -2248,11 +2284,14 @@ void R_DrawParticles (void)
        decalfade = frametime * 255 / cl_decals_fadetime.value;
        update = frametime > 0;
 
-       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,29 +2343,7 @@ 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)
@@ -2403,11 +2420,25 @@ void R_DrawParticles (void)
                else if (p->delayedspawn)
                        continue;
 
+               // skip some of the less important particles according to cl_minfps
+               if ((i & qualitymask) && p->qualityreduction)
+                       continue;
+
                // 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);
+       }
 }
index 1cacf76..0534d33 100644 (file)
@@ -32,7 +32,7 @@ cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1",
 cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output)"};
 cvar_t cl_capturevideo_width = {0, "cl_capturevideo_width", "0", "scales all frames to this resolution before saving the video"};
 cvar_t cl_capturevideo_height = {0, "cl_capturevideo_height", "0", "scales all frames to this resolution before saving the video"};
-cvar_t cl_capturevideo_realtime = {0, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over 1 second"};
+cvar_t cl_capturevideo_realtime = {0, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over a minute"};
 cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"};
 cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"};
 cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"};
@@ -629,7 +629,7 @@ void SCR_BeginLoadingPlaque (void)
 
 //=============================================================================
 
-char r_speeds_timestring[1024];
+char r_speeds_timestring[4096];
 int speedstringcount, r_timereport_active;
 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
 int r_speeds_longestitem = 0;
@@ -686,7 +686,7 @@ void R_TimeReport_EndFrame(void)
 {
        int i, j, lines, y;
        cl_locnode_t *loc;
-       char string[2048];
+       char string[1024+4096];
 
        string[0] = 0;
        if (r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected)
@@ -696,9 +696,9 @@ void R_TimeReport_EndFrame(void)
                loc = CL_Locs_FindNearest(cl.movement_origin);
                if (loc)
                        sprintf(string + strlen(string), "Location: %s\n", loc->name);
-               sprintf(string + strlen(string), "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], r_refdef.view.forward[0], r_refdef.view.forward[1], r_refdef.view.forward[2]);
+               sprintf(string + strlen(string), "%3i renders org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_refdef.stats.renders, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], r_refdef.view.forward[0], r_refdef.view.forward[1], r_refdef.view.forward[2]);
                sprintf(string + strlen(string), "%7i surfaces%7i triangles %5i entities (%7i surfaces%7i triangles)\n", r_refdef.stats.world_surfaces, r_refdef.stats.world_triangles, r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles);
-               sprintf(string + strlen(string), "%5i leafs%5i portals%6i particles%6i decals\n", r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles, r_refdef.stats.decals);
+               sprintf(string + strlen(string), "%5i leafs%5i portals%6i/%6i particles%6i/%6i decals %3i%% quality\n", r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles, cl.num_particles, r_refdef.stats.decals, cl.num_decals, 100 / (1 << r_refdef.view.qualityreduction));
                sprintf(string + strlen(string), "%7i lightmap updates (%7i pixels)\n", r_refdef.stats.lightmapupdates, r_refdef.stats.lightmapupdatepixels);
                sprintf(string + strlen(string), "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n", r_refdef.stats.lights, r_refdef.stats.lights_clears, r_refdef.stats.lights_scissored, r_refdef.stats.lights_lighttriangles, r_refdef.stats.lights_shadowtriangles, r_refdef.stats.lights_dynamicshadowtriangles);
                if (r_refdef.stats.bloom)
@@ -1631,7 +1631,7 @@ void SCR_CaptureVideo(void)
                else
                        newframenum = cls.capturevideo.frame + 1;
                // if falling behind more than one second, stop
-               if (newframenum - cls.capturevideo.frame > (int)ceil(cls.capturevideo.framerate))
+               if (newframenum - cls.capturevideo.frame > 60 * (int)ceil(cls.capturevideo.framerate))
                {
                        Cvar_SetValueQuick(&cl_capturevideo, 0);
                        Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame);
@@ -1731,6 +1731,7 @@ static void R_Envmap_f (void)
        {
                sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
                Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1);
+               r_refdef.view.qualityreduction = 0;
                r_refdef.view.clear = true;
                R_Mesh_Start();
                R_RenderView();
@@ -2092,8 +2093,15 @@ void SCR_UpdateLoadingScreen (qboolean clear)
        qglFinish();
 }
 
+extern cvar_t cl_minfps;
+extern cvar_t cl_minfps_expbase;
+extern cvar_t cl_minfps_fade;
+extern cvar_t cl_minfps_maxqualityreduction;
+static double cl_updatescreen_rendertime = 0;
+static double cl_updatescreen_qualityreduction = 0;
 void CL_UpdateScreen(void)
 {
+       double rendertime1;
        float conwidth, conheight;
 
        if (vid_hidden || !scr_refresh.integer)
@@ -2102,6 +2110,8 @@ void CL_UpdateScreen(void)
        if (!scr_initialized || !con_initialized)
                return;                         // not initialized yet
 
+       rendertime1 = Sys_DoubleTime();
+
        conwidth = bound(320, vid_conwidth.value, 2048);
        conheight = bound(200, vid_conheight.value, 1536);
        if (vid_conwidth.value != conwidth)
@@ -2159,6 +2169,7 @@ void CL_UpdateScreen(void)
        qglClearColor(0,0,0,0);CHECKGLERROR
        R_ClearScreen(false);
        r_refdef.view.clear = false;
+       r_refdef.view.qualityreduction = (int)floor(cl_updatescreen_qualityreduction + 0.5);
 
        if(scr_stipple.integer)
        {
@@ -2229,6 +2240,15 @@ void CL_UpdateScreen(void)
 
        SCR_CaptureVideo();
 
+       // quality adjustment according to render time
+       qglFlush();
+       cl_updatescreen_rendertime += ((Sys_DoubleTime() - rendertime1) - cl_updatescreen_rendertime) * bound(0.01, cl_minfps_fade.value, 1);
+       if (cl_minfps.value > 0 && !cls.timedemo && (!cls.capturevideo.active || !cls.capturevideo.realtime))
+               cl_updatescreen_qualityreduction = invpow(cl_minfps_expbase.value, cl_minfps.value * cl_updatescreen_rendertime);
+       else
+               cl_updatescreen_qualityreduction = 0;
+       cl_updatescreen_qualityreduction = bound(0, cl_updatescreen_qualityreduction, bound(0, cl_minfps_maxqualityreduction.value, 30));
+
        VID_Finish(true);
 }
 
index 6546773..245d590 100644 (file)
--- a/client.h
+++ b/client.h
@@ -646,7 +646,8 @@ typedef struct decal_s
        vec3_t                  normal;
        float                   size;
        float                   alpha; // 0-255
-       unsigned char   color[4];
+       unsigned char   color[3];
+       unsigned char   unused1;
 
        // fields not used by rendering: (36 bytes in 32bit, 40 bytes in 64bit)
        float                   time2; // used for decal fade
@@ -654,7 +655,6 @@ typedef struct decal_s
        model_t                 *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive)
        vec3_t                  relativeorigin; // decal at this location in entity's coordinate space
        vec3_t                  relativenormal; // decal oriented this way relative to entity's coordinate space
-
 }
 decal_t;
 
@@ -667,7 +667,8 @@ typedef struct particle_s
        vec3_t                  vel; // velocity of particle, or orientation of decal, or end point of beam
        float                   size;
        float                   alpha; // 0-255
-       unsigned char   color[4];
+       unsigned char   color[3];
+       unsigned char   qualityreduction; // enables skipping of this particle according to r_refdef.view.qualityreduction
 
        // fields not used by rendering:  (40 bytes)
        float                   sizeincrease; // rate of size change per second
@@ -1328,6 +1329,7 @@ float FogForDistance(vec_t dist);
 
 typedef struct r_refdef_stats_s
 {
+       int renders;
        int entities;
        int entities_surfaces;
        int entities_triangles;
@@ -1407,6 +1409,12 @@ typedef struct r_refdef_view_s
        // these define which values to use in GL_CullFace calls to request frontface or backface culling
        int cullface_front;
        int cullface_back;
+
+       // reduces render quality:
+       // 0 = full quality
+       // 1 = skip every other particle and some lights
+       // 2 = skip 75% of particles and some lights
+       int qualityreduction;
 }
 r_refdef_view_t;
 
index 97224e1..c2f6ad2 100644 (file)
@@ -3478,6 +3478,8 @@ static void R_DrawLocs(void);
 static void R_DrawEntityBBoxes(void);
 void R_RenderScene(qboolean addwaterplanes)
 {
+       r_refdef.stats.renders++;
+
        R_UpdateFogColor();
 
        if (addwaterplanes)
diff --git a/host.c b/host.c
index 7862e44..fac4fa4 100644 (file)
--- a/host.c
+++ b/host.c
@@ -59,7 +59,10 @@ jmp_buf host_abortframe;
 cvar_t host_framerate = {0, "host_framerate","0", "locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed"};
 // shows time used by certain subsystems
 cvar_t host_speeds = {0, "host_speeds","0", "reports how much time is used in server/graphics/sound"};
-// LordHavoc: framerate upper cap
+cvar_t cl_minfps = {CVAR_SAVE, "cl_minfps", "40", "minimum fps target - while the rendering performance is below this, it will drift toward lower quality"};
+cvar_t cl_minfps_expbase = {CVAR_SAVE, "cl_minfps_expbase", "1.2", "base for log() function in calculating quality reduction, should be in the range 1.1 to 2.0"};
+cvar_t cl_minfps_fade = {CVAR_SAVE, "cl_minfps_fade", "0.2", "how fast the quality reduction adapts to varying framerate"};
+cvar_t cl_minfps_maxqualityreduction = {CVAR_SAVE, "cl_minfps_maxqualityreduction", "3", "how much particle quality can be reduced (as a power of 2) when framerate is staying below cl_minfps, 0 = no reduction, 1 = 50% quality, 2 = 25% quality, 3 = 12.5% quality, ..."};
 cvar_t cl_maxfps = {CVAR_SAVE, "cl_maxfps", "1000000", "maximum fps cap, if game is running faster than this it will wait before running another frame (useful to make cpu time available to other programs)"};
 cvar_t cl_maxidlefps = {CVAR_SAVE, "cl_maxidlefps", "20", "maximum fps cap when the game is not the active window (makes cpu time available to other programs"};
 
@@ -202,6 +205,10 @@ static void Host_InitLocal (void)
 
        Cvar_RegisterVariable (&host_framerate);
        Cvar_RegisterVariable (&host_speeds);
+       Cvar_RegisterVariable (&cl_minfps);
+       Cvar_RegisterVariable (&cl_minfps_expbase);
+       Cvar_RegisterVariable (&cl_minfps_fade);
+       Cvar_RegisterVariable (&cl_minfps_maxqualityreduction);
        Cvar_RegisterVariable (&cl_maxfps);
        Cvar_RegisterVariable (&cl_maxidlefps);
 
diff --git a/menu.c b/menu.c
index c7ca69c..a48e89a 100644 (file)
--- a/menu.c
+++ b/menu.c
@@ -5073,6 +5073,7 @@ void MP_Draw (void)
        // declarations that are needed right now
        extern r_refdef_scene_t menu_scene;
 
+       int oldqualityreduction;
        static r_refdef_scene_t clientscene;
        clientscene = r_refdef.scene;
        r_refdef.scene = menu_scene;
@@ -5080,6 +5081,10 @@ void MP_Draw (void)
        // reset the temp entities each frame
        r_refdef.scene.numtempentities = 0;
 
+       // menu scenes do not use reduced rendering quality
+       oldqualityreduction = r_refdef.view.qualityreduction;
+       r_refdef.view.qualityreduction = 0;
+
        PRVM_Begin;
        PRVM_SetProg(PRVM_MENUPROG);
 
@@ -5089,6 +5094,8 @@ void MP_Draw (void)
 
        PRVM_End;
 
+       r_refdef.view.qualityreduction = oldqualityreduction;
+
        menu_scene = r_refdef.scene;
        r_refdef.scene = clientscene;
 }