]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
default effectinfo velocitymultiplier to 0 in nexuiz mode
[xonotic/darkplaces.git] / cl_particles.c
index 696b3e16629f1b17b6890fbcbc5923ba37a4095b..ab4f8209d460476449ea9b3a0c1bcf949ec8e713 100644 (file)
@@ -192,6 +192,81 @@ static const int tex_bubble = 62;
 static const int tex_raindrop = 61;
 static const int tex_beam = 60;
 
+particleeffectinfo_t baselineparticleeffectinfo =
+{
+       0, //int effectnameindex; // which effect this belongs to
+       // PARTICLEEFFECT_* bits
+       0, //int flags;
+       // blood effects may spawn very few particles, so proper fraction-overflow
+       // handling is very important, this variable keeps track of the fraction
+       0.0, //double particleaccumulator;
+       // the math is: countabsolute + requestedcount * countmultiplier * quality
+       // absolute number of particles to spawn, often used for decals
+       // (unaffected by quality and requestedcount)
+       0.0f, //float countabsolute;
+       // multiplier for the number of particles CL_ParticleEffect was told to
+       // spawn, most effects do not really have a count and hence use 1, so
+       // this is often the actual count to spawn, not merely a multiplier
+       0.0f, //float countmultiplier;
+       // if > 0 this causes the particle to spawn in an evenly spaced line from
+       // originmins to originmaxs (causing them to describe a trail, not a box)
+       0.0f, //float trailspacing;
+       // type of particle to spawn (defines some aspects of behavior)
+       pt_alphastatic, //ptype_t particletype;
+       // blending mode used on this particle type
+       PBLEND_ALPHA, //pblend_t blendmode;
+       // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
+       PARTICLE_BILLBOARD, //porientation_t orientation;
+       // range of colors to choose from in hex RRGGBB (like HTML color tags),
+       // randomly interpolated at spawn
+       {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
+       // a random texture is chosen in this range (note the second value is one
+       // past the last choosable, so for example 8,16 chooses any from 8 up and
+       // including 15)
+       // if start and end of the range are the same, no randomization is done
+       {63, 63 /* tex_particle */}, //int tex[2];
+       // range of size values randomly chosen when spawning, plus size increase over time
+       {1, 1, 1}, //float size[3];
+       // range of alpha values randomly chosen when spawning, plus alpha fade
+       {0.0f, 256.0f, 256.0f}, //float alpha[3];
+       // how long the particle should live (note it is also removed if alpha drops to 0)
+       {16777216.0f, 16777216.0f}, //float time[2];
+       // how much gravity affects this particle (negative makes it fly up!)
+       1.0f, //float gravity;
+       // how much bounce the particle has when it hits a surface
+       // if negative the particle is removed on impact
+       0.0f, //float bounce;
+       // if in air this friction is applied
+       // if negative the particle accelerates
+       0.0f, //float airfriction;
+       // if in liquid (water/slime/lava) this friction is applied
+       // if negative the particle accelerates
+       0.0f, //float liquidfriction;
+       // these offsets are added to the values given to particleeffect(), and
+       // then an ellipsoid-shaped jitter is added as defined by these
+       // (they are the 3 radii)
+       1.0f, //float stretchfactor;
+       // stretch velocity factor (used for sparks)
+       {0.0f, 0.0f, 0.0f}, //float originoffset[3];
+       {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
+       {0.0f, 0.0f, 0.0f}, //float originjitter[3];
+       {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
+       1.0f, //float velocitymultiplier;
+       // an effect can also spawn a dlight
+       0.0f, //float lightradiusstart;
+       0.0f, //float lightradiusfade;
+       16777216.0f, //float lighttime;
+       {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
+       true, //qboolean lightshadow;
+       0, //int lightcubemapnum;
+       {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
+       {-1, -1}, //int staintex[2];
+       {1.0f, 1.0f}, //float stainalpha[2];
+       {2.0f, 2.0f}, //float stainsize[2];
+       // other parameters
+       {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
+};
+
 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
@@ -219,8 +294,9 @@ cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes,
 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
-cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
+cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
+cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
@@ -288,37 +364,12 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, co
                                break;
                        }
                        info = particleeffectinfo + numparticleeffectinfo++;
+                       // copy entire info from baseline, then fix up the nameindex
+                       *info = baselineparticleeffectinfo;
                        info->effectnameindex = effectnameindex;
-                       info->particletype = pt_alphastatic;
-                       info->blendmode = particletype[info->particletype].blendmode;
-                       info->orientation = particletype[info->particletype].orientation;
-                       info->tex[0] = tex_particle;
-                       info->tex[1] = tex_particle;
-                       info->color[0] = 0xFFFFFF;
-                       info->color[1] = 0xFFFFFF;
-                       info->size[0] = 1;
-                       info->size[1] = 1;
-                       info->alpha[0] = 0;
-                       info->alpha[1] = 256;
-                       info->alpha[2] = 256;
-                       info->time[0] = 9999;
-                       info->time[1] = 9999;
-                       VectorSet(info->lightcolor, 1, 1, 1);
-                       info->lightshadow = true;
-                       info->lighttime = 9999;
-                       info->stretchfactor = 1;
-                       info->staincolor[0] = (unsigned int)-1;
-                       info->staincolor[1] = (unsigned int)-1;
-                       info->staintex[0] = -1;
-                       info->staintex[1] = -1;
-                       info->stainalpha[0] = 1;
-                       info->stainalpha[1] = 1;
-                       info->stainsize[0] = 2;
-                       info->stainsize[1] = 2;
-                       info->rotate[0] = 0;
-                       info->rotate[1] = 360;
-                       info->rotate[2] = 0;
-                       info->rotate[3] = 0;
+                       // Nexuiz had some busted effects that didn't specify this...
+                       if (gamemode == GAME_NEXUIZ)
+                               info->velocitymultiplier = 0.0f;
                }
                else if (info == NULL)
                {
@@ -533,6 +584,7 @@ void CL_Particles_Init (void)
        Cvar_RegisterVariable (&cl_decals_fadetime);
        Cvar_RegisterVariable (&cl_decals_newsystem);
        Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
+       Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
        Cvar_RegisterVariable (&cl_decals_models);
        Cvar_RegisterVariable (&cl_decals_bias);
        Cvar_RegisterVariable (&cl_decals_max);
@@ -600,6 +652,12 @@ particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, i
        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;
+       if (vid.sRGB3D)
+       {
+               part->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[0]) * 256.0f);
+               part->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[1]) * 256.0f);
+               part->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(part->color[2]) * 256.0f);
+       }
        part->alpha = palpha;
        part->alphafade = palphafade;
        part->staintexnum = staintex;
@@ -680,7 +738,7 @@ particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, i
                part->typeindex = pt_spark;
                part->bounce = 0;
                VectorMA(part->org, lifetime, part->vel, endvec);
-               trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
+               trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false);
                part->die = cl.time + lifetime * trace.fraction;
                part2 = CL_NewParticle(endvec, 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, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL);
                if (part2)
@@ -756,7 +814,10 @@ void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t
 
        if (cl_decals_newsystem.integer)
        {
-               R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
+               if (vid.sRGB3D)
+                       R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
+               else
+                       R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
                return;
        }
 
@@ -778,6 +839,12 @@ void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t
        decal->color[0] = color[0];
        decal->color[1] = color[1];
        decal->color[2] = color[2];
+       if (vid.sRGB3D)
+       {
+               decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
+               decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
+               decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
+       }
        decal->owner = hitent;
        decal->clusterindex = -1000; // no vis culling unless we're sure
        if (hitent)
@@ -810,7 +877,7 @@ void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size,
        {
                VectorRandom(org2);
                VectorMA(org, maxdist, org2, org2);
-               trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
+               trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true);
                // take the closest trace result that doesn't end up hitting a NOMARKS
                // surface (sky for example)
                if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
@@ -840,7 +907,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                {
                        // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
                        if (count == 1024)
-                               CL_ParticleExplosion(center);
+                               CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
                        else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
                                CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
                        else
@@ -949,7 +1016,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                else
                {
                        static double bloodaccumulator = 0;
-                       qboolean immediatebloodstain = true;
+                       qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
                        //CL_NewParticle(center, 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, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, NULL);
                        bloodaccumulator += count * 0.333 * cl_particles_quality.value;
                        for (;bloodaccumulator > 0;bloodaccumulator--)
@@ -1472,7 +1539,10 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                        {
                                                info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
                                                trailstep = 0;
-                                               immediatebloodstain = info->particletype == pt_blood || staintex;
+                                               immediatebloodstain =
+                                                       ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
+                                                       ||
+                                                       ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
                                        }
                                        info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
                                        for (;info->particleaccumulator >= 1;info->particleaccumulator--)
@@ -1682,16 +1752,15 @@ void CL_ParticleExplosion (const vec3_t org)
                        {
                                for (i = 0;i < 512 * cl_particles_quality.value;i++)
                                {
-                                       int k;
+                                       int k = 0;
                                        vec3_t v, v2;
-                                       for (k = 0;k < 16;k++)
+                                       do
                                        {
                                                VectorRandom(v2);
                                                VectorMA(org, 128, v2, v);
-                                               trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
-                                               if (trace.fraction >= 0.1)
-                                                       break;
+                                               trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false);
                                        }
+                                       while (k < 16 && trace.fraction < 0.1f);
                                        VectorSubtract(trace.endpos, org, v2);
                                        VectorScale(v2, 2.0f, v2);
                                        CL_NewParticle(org, 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, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
@@ -1820,9 +1889,11 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
        }
 }
 
-static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
+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_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
+static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
+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
@@ -1936,7 +2007,7 @@ static void R_InitBloodTextures (unsigned char *particletexturedata)
 {
        int i, j, k, m;
        size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
-       unsigned char *data = Mem_Alloc(tempmempool, datasize);
+       unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
 
        // blood particles
        for (i = 0;i < 8;i++)
@@ -1977,6 +2048,7 @@ static void R_InitParticleTexture (void)
        char *buf;
        fs_offset_t filesize;
        char texturename[MAX_QPATH];
+       skinframe_t *sf;
 
        // a note: decals need to modulate (multiply) the background color to
        // properly darken it (stain), and they need to be able to alpha fade,
@@ -1988,7 +2060,7 @@ static void R_InitParticleTexture (void)
        // we invert it again during the blendfunc to make it work...
 
 #ifndef DUMPPARTICLEFONT
-       decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
+       decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
        if (decalskinframe)
        {
                particlefonttexture = decalskinframe->base;
@@ -2133,7 +2205,7 @@ static void R_InitParticleTexture (void)
                Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
 #endif
 
-               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
+               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
                particlefonttexture = decalskinframe->base;
 
                Mem_Free(particletexturedata);
@@ -2152,7 +2224,7 @@ static void R_InitParticleTexture (void)
        }
 
 #ifndef DUMPPARTICLEFONT
-       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
+       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
        if (!particletexture[tex_beam].texture)
 #endif
        {
@@ -2175,7 +2247,7 @@ static void R_InitParticleTexture (void)
 #ifdef DUMPPARTICLEFONT
                Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
 #endif
-               particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL);
+               particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
        }
        particletexture[tex_beam].s1 = 0;
        particletexture[tex_beam].t1 = 0;
@@ -2204,9 +2276,11 @@ static void R_InitParticleTexture (void)
 
                        if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
                        {
+                               strlcpy(texturename, com_token, sizeof(texturename));
                                s1 = atof(com_token);
                                if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
                                {
+                                       texturename[0] = 0;
                                        t1 = atof(com_token);
                                        if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
                                        {
@@ -2221,10 +2295,7 @@ static void R_InitParticleTexture (void)
                                        }
                                }
                                else
-                               {
                                        s1 = 0;
-                                       strlcpy(texturename, com_token, sizeof(texturename));
-                               }
                        }
                        if (!texturename[0])
                        {
@@ -2236,7 +2307,13 @@ static void R_InitParticleTexture (void)
                                Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
                                continue;
                        }
-                       particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
+                       sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true);
+                       if(!sf)
+                       {
+                               // R_SkinFrame_LoadExternal already complained
+                               continue;
+                       }
+                       particletexture[i].texture = sf->base;
                        particletexture[i].s1 = s1;
                        particletexture[i].t1 = t1;
                        particletexture[i].s2 = s2;
@@ -2269,14 +2346,13 @@ static void r_part_newmap(void)
        CL_Particles_LoadEffectInfo();
 }
 
-#define BATCHSIZE 256
-unsigned short particle_elements[BATCHSIZE*6];
-float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
+unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
+float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
 
 void R_Particles_Init (void)
 {
        int i;
-       for (i = 0;i < BATCHSIZE;i++)
+       for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
        {
                particle_elements[i*6+0] = i*4+0;
                particle_elements[i*6+1] = i*4+1;
@@ -2288,9 +2364,11 @@ void R_Particles_Init (void)
 
        Cvar_RegisterVariable(&r_drawparticles);
        Cvar_RegisterVariable(&r_drawparticles_drawdistance);
+       Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
+       Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
        Cvar_RegisterVariable(&r_drawdecals);
        Cvar_RegisterVariable(&r_drawdecals_drawdistance);
-       R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
+       R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
 }
 
 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
@@ -2305,7 +2383,7 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        RSurf_ActiveWorldEntity();
 
        r_refdef.stats.drawndecals += numsurfaces;
-       R_Mesh_ResetTextureState();
+//     R_Mesh_ResetTextureState();
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
        GL_PolygonOffset(0, 0);
@@ -2361,7 +2439,7 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        // now render the decals all at once
        // (this assumes they all use one particle font texture!)
        GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
-       R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
+       R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false);
        R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
        R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
 }
@@ -2454,40 +2532,48 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        float *v3f, *t2f, *c4f;
        particletexture_t *tex;
        float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
-       float ambient[3], diffuse[3], diffusenormal[3];
-       float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
+//     float ambient[3], diffuse[3], diffusenormal[3];
+       float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
        vec4_t colormultiplier;
+       float minparticledist_start, minparticledist_end;
+       qboolean dofade;
 
        RSurf_ActiveWorldEntity();
 
        Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
 
        r_refdef.stats.particles += numsurfaces;
-       R_Mesh_ResetTextureState();
+//     R_Mesh_ResetTextureState();
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
        GL_PolygonOffset(0, 0);
        GL_DepthTest(true);
-       GL_AlphaTest(false);
        GL_CullFace(GL_NONE);
 
        spintime = r_refdef.scene.time;
 
+       minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
+       minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
+       dofade = (minparticledist_start < minparticledist_end);
+
        // first generate all the vertices at once
        for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
        {
                p = cl.particles + surfacelist[surfacelistindex];
 
                blendmode = (pblend_t)p->blendmode;
+               palpha = p->alpha;
+               if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
+                       palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward)  - minparticledist_start) / (minparticledist_end - minparticledist_start));
+               alpha = palpha * colormultiplier[3];
+               // ensure alpha multiplier saturates properly
+               if (alpha > 1.0f)
+                       alpha = 1.0f;
 
                switch (blendmode)
                {
                case PBLEND_INVALID:
                case PBLEND_INVMOD:
-                       alpha = p->alpha * colormultiplier[3];
-                       // ensure alpha multiplier saturates properly
-                       if (alpha > 1.0f)
-                               alpha = 1.0f;
                        // additive and modulate can just fade out in fog (this is correct)
                        if (r_refdef.fogenabled)
                                alpha *= RSurf_FogVertex(p->org);
@@ -2496,13 +2582,9 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        c4f[0] = p->color[0] * alpha;
                        c4f[1] = p->color[1] * alpha;
                        c4f[2] = p->color[2] * alpha;
-                       c4f[3] = 1;
+                       c4f[3] = 0;
                        break;
                case PBLEND_ADD:
-                       alpha = p->alpha * colormultiplier[3];
-                       // ensure alpha multiplier saturates properly
-                       if (alpha > 1.0f)
-                               alpha = 1.0f;
                        // additive and modulate can just fade out in fog (this is correct)
                        if (r_refdef.fogenabled)
                                alpha *= RSurf_FogVertex(p->org);
@@ -2510,21 +2592,16 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        c4f[0] = p->color[0] * colormultiplier[0] * alpha;
                        c4f[1] = p->color[1] * colormultiplier[1] * alpha;
                        c4f[2] = p->color[2] * colormultiplier[2] * alpha;
-                       c4f[3] = 1;
+                       c4f[3] = 0;
                        break;
                case PBLEND_ALPHA:
                        c4f[0] = p->color[0] * colormultiplier[0];
                        c4f[1] = p->color[1] * colormultiplier[1];
                        c4f[2] = p->color[2] * colormultiplier[2];
-                       c4f[3] = p->alpha * colormultiplier[3];
+                       c4f[3] = alpha;
                        // note: lighting is not cheap!
                        if (particletype[p->typeindex].lighting)
-                       {
-                               R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
-                               c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
-                               c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
-                               c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
-                       }
+                               R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
                        // mix in the fog color
                        if (r_refdef.fogenabled)
                        {
@@ -2534,6 +2611,8 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                                c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
                                c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
                        }
+                       // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
+                       VectorScale(c4f, alpha, c4f);
                        break;
                }
                // copy the color into the other three vertices
@@ -2667,36 +2746,38 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        {
                p = cl.particles + surfacelist[surfacelistindex];
 
-               if (blendmode != p->blendmode)
-               {
-                       blendmode = (pblend_t)p->blendmode;
-                       switch(blendmode)
-                       {
-                       case PBLEND_ALPHA:
-                               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-                               break;
-                       case PBLEND_INVALID:
-                       case PBLEND_ADD:
-                               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
-                               break;
-                       case PBLEND_INVMOD:
-                               GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
-                               break;
-                       }
-               }
                if (texture != particletexture[p->texnum].texture)
                {
                        texture = particletexture[p->texnum].texture;
-                       R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
+                       R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false);
                }
 
-               // iterate until we find a change in settings
-               batchstart = surfacelistindex++;
-               for (;surfacelistindex < numsurfaces;surfacelistindex++)
+               if (p->blendmode == PBLEND_INVMOD)
                {
-                       p = cl.particles + surfacelist[surfacelistindex];
-                       if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
-                               break;
+                       // inverse modulate blend - group these
+                       GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+                       // iterate until we find a change in settings
+                       batchstart = surfacelistindex++;
+                       for (;surfacelistindex < numsurfaces;surfacelistindex++)
+                       {
+                               p = cl.particles + surfacelist[surfacelistindex];
+                               if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
+                                       break;
+                       }
+               }
+               else
+               {
+                       // additive or alpha blend - group these
+                       // (we can group these because we premultiplied the texture alpha)
+                       GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+                       // iterate until we find a change in settings
+                       batchstart = surfacelistindex++;
+                       for (;surfacelistindex < numsurfaces;surfacelistindex++)
+                       {
+                               p = cl.particles + surfacelist[surfacelistindex];
+                               if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
+                                       break;
+                       }
                }
 
                batchcount = surfacelistindex - batchstart;
@@ -2708,7 +2789,7 @@ void R_DrawParticles (void)
 {
        int i, a;
        int drawparticles = r_drawparticles.integer;
-       float minparticledist;
+       float minparticledist_start;
        particle_t *p;
        float gravity, frametime, f, dist, oldorg[3];
        float drawdist2;
@@ -2723,7 +2804,7 @@ void R_DrawParticles (void)
        if (!cl.num_particles)
                return;
 
-       minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
+       minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
        gravity = frametime * cl.movevars_gravity;
        update = frametime > 0;
        drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
@@ -2773,9 +2854,9 @@ void R_DrawParticles (void)
                                VectorCopy(p->org, oldorg);
                                VectorMA(p->org, frametime, p->vel, p->org);
 //                             if (p->bounce && cl.time >= p->delayedcollisions)
-                               if (p->bounce && cl_particles_collisions.integer)
+                               if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
                                {
-                                       trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
+                                       trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false, false);
                                        // if the trace started in or hit something of SUPERCONTENTS_NODROP
                                        // or if the trace hit something flagged as NOIMPACT
                                        // then remove the particle
@@ -2830,11 +2911,16 @@ void R_DrawParticles (void)
                                                        // anything else - bounce off solid
                                                        dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
                                                        VectorMA(p->vel, dist, trace.plane.normal, p->vel);
-                                                       if (DotProduct(p->vel, p->vel) < 0.03)
-                                                               VectorClear(p->vel);
                                                }
                                        }
                                }
+
+                               if (VectorLength2(p->vel) < 0.03)
+                               {
+                                       if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
+                                               goto killparticle;
+                                       VectorClear(p->vel);
+                               }
                        }
 
                        if (p->typeindex != pt_static)
@@ -2904,7 +2990,7 @@ void R_DrawParticles (void)
                                                                continue;
                                        }
                        // 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))
+                       if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
                                R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
                        break;
                }