]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
cl_particles_forcetraileffects: emulate old trail behaviour (Xonotic 0.7 compatibility).
[xonotic/darkplaces.git] / cl_particles.c
index 01e9701c2f44e93f2ae6f7876a421e81fc6a27ae..bccd26e1463f52660966fc54ba9e4f4078baaff8 100644 (file)
@@ -114,6 +114,7 @@ typedef struct particleeffectinfo_s
        float lightcolor[3];
        qboolean lightshadow;
        int lightcubemapnum;
+       float lightcorona[2];
        unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
        int staintex[2];
        float stainalpha[2];
@@ -263,6 +264,7 @@ particleeffectinfo_t baselineparticleeffectinfo =
        {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
        true, //qboolean lightshadow;
        0, //int lightcubemapnum;
+       {1.0f, 0.25f}, //float lightcorona[2];
        {(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];
@@ -294,6 +296,7 @@ cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sp
 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
+cvar_t cl_particles_forcetraileffects = {0, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
 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"};
@@ -301,6 +304,7 @@ cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long dec
 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_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
 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"};
@@ -438,6 +442,7 @@ static void CL_Particles_ParseEffectInfo(const char *textstart, const char *text
                else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
                else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
                else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
+               else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
                else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
                else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
                else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
@@ -515,7 +520,7 @@ static const char *standardeffectnames[EFFECT_TOTAL] =
        "SVC_PARTICLE"
 };
 
-static void CL_Particles_LoadEffectInfo(void)
+static void CL_Particles_LoadEffectInfo(const char *customfile)
 {
        int i;
        int filepass;
@@ -530,10 +535,15 @@ static void CL_Particles_LoadEffectInfo(void)
        for (filepass = 0;;filepass++)
        {
                if (filepass == 0)
-                       dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
+               {
+                       if (customfile)
+                               strlcpy(filename, customfile, sizeof(filename));
+                       else
+                               strlcpy(filename, "effectinfo.txt", sizeof(filename));
+               }
                else if (filepass == 1)
                {
-                       if (!cl.worldbasename[0])
+                       if (!cl.worldbasename[0] || customfile)
                                continue;
                        dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
                }
@@ -547,6 +557,11 @@ static void CL_Particles_LoadEffectInfo(void)
        }
 }
 
+static void CL_Particles_LoadEffectInfo_f(void)
+{
+       CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
+}
+
 /*
 ===============
 CL_InitParticles
@@ -556,7 +571,7 @@ void CL_ReadPointFile_f (void);
 void CL_Particles_Init (void)
 {
        Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
-       Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
+       Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
 
        Cvar_RegisterVariable (&cl_particles);
        Cvar_RegisterVariable (&cl_particles_quality);
@@ -581,6 +596,7 @@ void CL_Particles_Init (void)
        Cvar_RegisterVariable (&cl_particles_bubbles);
        Cvar_RegisterVariable (&cl_particles_visculling);
        Cvar_RegisterVariable (&cl_particles_collisions);
+       Cvar_RegisterVariable (&cl_particles_forcetraileffects);
        Cvar_RegisterVariable (&cl_decals);
        Cvar_RegisterVariable (&cl_decals_visculling);
        Cvar_RegisterVariable (&cl_decals_time);
@@ -588,6 +604,7 @@ void CL_Particles_Init (void)
        Cvar_RegisterVariable (&cl_decals_newsystem);
        Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
        Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
+       Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
        Cvar_RegisterVariable (&cl_decals_models);
        Cvar_RegisterVariable (&cl_decals_bias);
        Cvar_RegisterVariable (&cl_decals_max);
@@ -871,8 +888,10 @@ void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t
 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
 {
        int i;
-       float bestfrac, bestorg[3], bestnormal[3];
-       float org2[3];
+       vec_t bestfrac;
+       vec3_t bestorg;
+       vec3_t bestnormal;
+       vec3_t org2;
        int besthitent = 0, hitent;
        trace_t trace;
        bestfrac = 10;
@@ -902,6 +921,7 @@ static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const v
        vec3_t center;
        matrix4x4_t tempmatrix;
        particle_t *part;
+
        VectorLerp(originmins, 0.5, originmaxs, center);
        Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
        if (effectnameindex == EFFECT_SVC_PARTICLE)
@@ -1193,6 +1213,8 @@ static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const v
        }
        else if (effectnameindex == EFFECT_EF_FLAME)
        {
+               if (!spawnparticles)
+                       count = 0;
                count *= 300 * cl_particles_quality.value;
                while (count-- > 0)
                        CL_NewParticle(center, 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, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
@@ -1200,6 +1222,8 @@ static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const v
        }
        else if (effectnameindex == EFFECT_EF_STARDUST)
        {
+               if (!spawnparticles)
+                       count = 0;
                count *= 200 * cl_particles_quality.value;
                while (count-- > 0)
                        CL_NewParticle(center, 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, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
@@ -1420,7 +1444,7 @@ static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const v
 // spawnparticles = true
 // it is called CL_ParticleTrail because most code does not want to supply
 // these parameters, only trail handling does
-void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4])
+static void CL_NewParticlesFromEffectinfo(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail)
 {
        qboolean found = false;
        char vabuf[1024];
@@ -1469,17 +1493,27 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                {
                        if (info->effectnameindex == effectnameindex)
                        {
+                               qboolean definedastrail = info->trailspacing > 0;
+
+                               qboolean drawastrail = wanttrail;
+                               if (cl_particles_forcetraileffects.integer)
+                                       drawastrail = drawastrail || definedastrail;
+
                                found = true;
                                if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
                                        continue;
                                if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
                                        continue;
 
+                               // trail effects may only ever be drawn as trail
+                               if (!drawastrail && definedastrail)
+                                       continue;
+
                                // spawn a dlight if requested
                                if (info->lightradiusstart > 0 && spawndlight)
                                {
                                        matrix4x4_t tempmatrix;
-                                       if (info->trailspacing > 0)
+                                       if (drawastrail)
                                                Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
                                        else
                                                Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
@@ -1487,7 +1521,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                        {
                                                // light flash (explosion, etc)
                                                // called when effect starts
-                                               CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                                               CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
                                        }
                                        else if (r_refdef.scene.numlights < MAX_DLIGHTS)
                                        {
@@ -1497,7 +1531,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                                rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
                                                rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
                                                rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
-                                               R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                                               R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
                                                r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
                                        }
                                }
@@ -1551,20 +1585,25 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                        default: break;
                                        }
                                        VectorCopy(originmins, trailpos);
-                                       if (info->trailspacing > 0)
+                                       if (drawastrail)
                                        {
-                                               info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
-                                               trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
+                                               float cnt = info->countabsolute;
+                                               cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
+                                               if (info->trailspacing > 0)
+                                                       cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
+                                               cnt *= fade;
+                                               info->particleaccumulator += cnt;
+                                               trailstep = traillen / cnt;
                                                immediatebloodstain = false;
 
                                                AnglesFromVectors(angles, traildir, NULL, false);
-                                               AngleVectors(angles, forward, right, up);
-                                               VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
-                                               VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
                                        }
                                        else
                                        {
-                                               info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
+                                               float cnt = info->countabsolute;
+                                               cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
+                                               cnt *= fade;
+                                               info->particleaccumulator += cnt;
                                                trailstep = 0;
                                                immediatebloodstain =
                                                        ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
@@ -1573,10 +1612,10 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
 
                                                VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
                                                AnglesFromVectors(angles, velocity, NULL, false);
-                                               AngleVectors(angles, forward, right, up);
-                                               VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
-                                               VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
                                        }
+                                       AngleVectors(angles, forward, right, up);
+                                       VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
+                                       VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
                                        info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
                                        for (;info->particleaccumulator >= 1;info->particleaccumulator--)
                                        {
@@ -1614,9 +1653,19 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
 }
 
+void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
+{
+       CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
+}
+
+void CL_ParticleBox(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade)
+{
+       CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
+}
+
 void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor)
 {
-       CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
+       CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
 }
 
 /*
@@ -1626,8 +1675,9 @@ CL_EntityParticles
 */
 void CL_EntityParticles (const entity_t *ent)
 {
-       int i;
-       float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
+       int i, j;
+       vec_t pitch, yaw, dist = 64, beamlength = 16;
+       vec3_t org, v;
        static vec3_t avelocities[NUMVERTEXNORMALS];
        if (!cl_particles.integer) return;
        if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
@@ -1635,8 +1685,9 @@ void CL_EntityParticles (const entity_t *ent)
        Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
 
        if (!avelocities[0][0])
-               for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
-                       avelocities[0][i] = lhrandom(0, 2.55);
+               for (i = 0;i < NUMVERTEXNORMALS;i++)
+                       for (j = 0;j < 3;j++)
+                               avelocities[i][j] = lhrandom(0, 2.55);
 
        for (i = 0;i < NUMVERTEXNORMALS;i++)
        {
@@ -1652,7 +1703,8 @@ void CL_EntityParticles (const entity_t *ent)
 
 void CL_ReadPointFile_f (void)
 {
-       vec3_t org, leakorg;
+       double org[3], leakorg[3];
+       vec3_t vecorg;
        int r, c, s;
        char *pointfile = NULL, *pointfilepos, *t, tchar;
        char name[MAX_QPATH];
@@ -1687,7 +1739,8 @@ void CL_ReadPointFile_f (void)
 #if _MSC_VER >= 1400
 #define sscanf sscanf_s
 #endif
-               r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
+               r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
+               VectorCopy(org, vecorg);
                *t = tchar;
                pointfilepos = t;
                if (r != 3)
@@ -1699,16 +1752,16 @@ void CL_ReadPointFile_f (void)
                if (cl.num_particles < cl.max_particles - 3)
                {
                        s++;
-                       CL_NewParticle(org, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
+                       CL_NewParticle(vecorg, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
                }
        }
        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]);
+       VectorCopy(leakorg, vecorg);
+       Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
 
-       CL_NewParticle(org, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
-       CL_NewParticle(org, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
-       CL_NewParticle(org, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
+       CL_NewParticle(vecorg, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
+       CL_NewParticle(vecorg, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
+       CL_NewParticle(vecorg, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
 }
 
 /*
@@ -2366,7 +2419,7 @@ static void r_part_start(void)
                particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
        particletexturepool = R_AllocTexturePool();
        R_InitParticleTexture ();
-       CL_Particles_LoadEffectInfo();
+       CL_Particles_LoadEffectInfo(NULL);
 }
 
 static void r_part_shutdown(void)
@@ -2378,7 +2431,7 @@ static void r_part_newmap(void)
 {
        if (decalskinframe)
                R_SkinFrame_MarkUsed(decalskinframe);
-       CL_Particles_LoadEffectInfo();
+       CL_Particles_LoadEffectInfo(NULL);
 }
 
 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
@@ -2412,12 +2465,12 @@ static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rt
        const decal_t *d;
        float *v3f, *t2f, *c4f;
        particletexture_t *tex;
-       float right[3], up[3], size, ca;
+       vec_t right[3], up[3], size, ca;
        float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
 
        RSurf_ActiveWorldEntity();
 
-       r_refdef.stats.drawndecals += numsurfaces;
+       r_refdef.stats[r_stat_drawndecals] += numsurfaces;
 //     R_Mesh_ResetTextureState();
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
@@ -2533,7 +2586,7 @@ void R_DrawDecals (void)
                        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(MESHQUEUE_SORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
+                       R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
                continue;
 killdecal:
                decal->typeindex = 0;
@@ -2554,11 +2607,12 @@ killdecal:
                Mem_Free(olddecals);
        }
 
-       r_refdef.stats.totaldecals = cl.num_decals;
+       r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
 }
 
 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
 {
+       vec3_t vecorg, vecvel, baseright, baseup;
        int surfacelistindex;
        int batchstart, batchcount;
        const particle_t *p;
@@ -2568,7 +2622,7 @@ static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const
        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 palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
+       float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
        vec4_t colormultiplier;
        float minparticledist_start, minparticledist_end;
        qboolean dofade;
@@ -2577,7 +2631,7 @@ static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const
 
        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_refdef.stats[r_stat_particles] += numsurfaces;
 //     R_Mesh_ResetTextureState();
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
@@ -2636,7 +2690,12 @@ static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const
                        c4f[3] = alpha;
                        // note: lighting is not cheap!
                        if (particletype[p->typeindex].lighting)
-                               R_LightPoint(c4f, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
+                       {
+                               vecorg[0] = p->org[0];
+                               vecorg[1] = p->org[1];
+                               vecorg[2] = p->org[2];
+                               R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
+                       }
                        // mix in the fog color
                        if (r_refdef.fogenabled)
                        {
@@ -2697,7 +2756,10 @@ static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const
                        t2f[6] = tex->s2;t2f[7] = tex->t2;
                        break;
                case PARTICLE_ORIENTED_DOUBLESIDED:
-                       VectorVectors(p->vel, baseright, baseup);
+                       vecvel[0] = p->vel[0];
+                       vecvel[1] = p->vel[1];
+                       vecvel[2] = p->vel[2];
+                       VectorVectors(vecvel, baseright, baseup);
                        if (p->angle + p->spin)
                        {
                                spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
@@ -2826,7 +2888,7 @@ void R_DrawParticles (void)
        int drawparticles = r_drawparticles.integer;
        float minparticledist_start;
        particle_t *p;
-       float gravity, frametime, f, dist, oldorg[3];
+       float gravity, frametime, f, dist, oldorg[3], decaldir[3];
        float drawdist2;
        int hitent;
        trace_t trace;
@@ -2915,7 +2977,14 @@ void R_DrawParticles (void)
                                                                {
                                                                        // create a decal for the blood splat
                                                                        a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
-                                                                       CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
+                                                                       if (cl_decals_newsystem_bloodsmears.integer)
+                                                                       {
+                                                                               VectorCopy(p->vel, decaldir);
+                                                                               VectorNormalize(decaldir);
+                                                                       }
+                                                                       else
+                                                                               VectorCopy(trace.plane.normal, decaldir);
+                                                                       CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
                                                                }
                                                        }
                                                }
@@ -2931,7 +3000,14 @@ void R_DrawParticles (void)
                                                                if (cl_decals.integer)
                                                                {
                                                                        // create a decal for the blood splat
-                                                                       CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
+                                                                       if (cl_decals_newsystem_bloodsmears.integer)
+                                                                       {
+                                                                               VectorCopy(p->vel, decaldir);
+                                                                               VectorNormalize(decaldir);
+                                                                       }
+                                                                       else
+                                                                               VectorCopy(trace.plane.normal, decaldir);
+                                                                       CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
                                                                }
                                                        }
                                                        goto killparticle;
@@ -3012,7 +3088,7 @@ void R_DrawParticles (void)
                {
                case pt_beam:
                        // beams have no culling
-                       R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
+                       R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
                        break;
                default:
                        if(cl_particles_visculling.integer)
@@ -3026,7 +3102,7 @@ void R_DrawParticles (void)
                                        }
                        // 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_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
-                               R_MeshQueue_AddTransparent(MESHQUEUE_SORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
+                               R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
                        break;
                }