]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
Configure all of the CL_MeshEntities* meshes the same, the material flags decide...
[xonotic/darkplaces.git] / cl_particles.c
index 187934c32d4dd69bf81ea46a526896c0a6d197b4..0fe0be4d09c7263c61ed59857b0899ac13b61601 100644 (file)
@@ -44,6 +44,7 @@ particletype_t particletype[pt_total] =
 
 #define PARTICLEEFFECT_UNDERWATER 1
 #define PARTICLEEFFECT_NOTUNDERWATER 2
+#define PARTICLEEFFECT_DEFINED 2147483648U
 
 typedef struct particleeffectinfo_s
 {
@@ -101,7 +102,9 @@ typedef struct particleeffectinfo_s
        float stretchfactor;
        // stretch velocity factor (used for sparks)
        float originoffset[3];
+       float relativeoriginoffset[3];
        float velocityoffset[3];
+       float relativevelocityoffset[3];
        float originjitter[3];
        float velocityjitter[3];
        float velocitymultiplier;
@@ -112,6 +115,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];
@@ -192,6 +196,84 @@ 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, 0.0f}, //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!)
+       0.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 relativeoriginoffset[3];
+       {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
+       {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
+       {0.0f, 0.0f, 0.0f}, //float originjitter[3];
+       {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
+       0.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;
+       {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];
+       {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"};
@@ -215,6 +297,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"};
@@ -222,15 +305,17 @@ 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"};
 
 
-void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
+static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
 {
        int arrayindex;
        int argc;
+       int i;
        int linenumber;
        particleeffectinfo_t *info = NULL;
        const char *text = textstart;
@@ -242,7 +327,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, co
                        argv[arrayindex][0] = 0;
                for (;;)
                {
-                       if (!COM_ParseToken_Simple(&text, true, false))
+                       if (!COM_ParseToken_Simple(&text, true, false, true))
                                return;
                        if (!strcmp(com_token, "\n"))
                                break;
@@ -288,45 +373,29 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, co
                                Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
                                break;
                        }
+                       for(i = 0; i < numparticleeffectinfo; ++i)
+                       {
+                               info = particleeffectinfo + i;
+                               if(!(info->flags & PARTICLEEFFECT_DEFINED))
+                                       if(info->effectnameindex == effectnameindex)
+                                               break;
+                       }
+                       if(i < numparticleeffectinfo)
+                               continue;
                        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;
+                       continue;
                }
                else if (info == NULL)
                {
                        Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
                        break;
                }
-               else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
+
+               info->flags |= PARTICLEEFFECT_DEFINED;
+               if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
                else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
                else if (!strcmp(argv[0], "type"))
                {
@@ -375,7 +444,9 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, co
                else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
                else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
                else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
+               else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
                else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
+               else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
                else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
                else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
                else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
@@ -385,6 +456,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, co
                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;}
@@ -462,7 +534,7 @@ static const char *standardeffectnames[EFFECT_TOTAL] =
        "SVC_PARTICLE"
 };
 
-void CL_Particles_LoadEffectInfo(void)
+static void CL_Particles_LoadEffectInfo(const char *customfile)
 {
        int i;
        int filepass;
@@ -477,10 +549,15 @@ 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);
                }
@@ -494,6 +571,11 @@ void CL_Particles_LoadEffectInfo(void)
        }
 }
 
+static void CL_Particles_LoadEffectInfo_f(void)
+{
+       CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
+}
+
 /*
 ===============
 CL_InitParticles
@@ -503,7 +585,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);
@@ -528,6 +610,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);
@@ -535,6 +618,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);
@@ -602,6 +686,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)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
+               part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
+               part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
+       }
        part->alpha = palpha;
        part->alphafade = palphafade;
        part->staintexnum = staintex;
@@ -675,14 +765,13 @@ particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, i
        {
                int i;
                particle_t *part2;
-               float lifetime = part->die - cl.time;
                vec3_t endvec;
                trace_t trace;
                // turn raindrop into simple spark and create delayedspawn splash effect
                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_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, 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)
@@ -758,7 +847,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;
        }
 
@@ -780,6 +872,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)
@@ -803,8 +901,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;
@@ -812,7 +912,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, 0, 0, collision_extendmovelength.value, 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))
@@ -829,22 +929,24 @@ void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size,
 
 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
-void CL_ParticleEffect_Fallback(int effectnameindex, float count, 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)
+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);
+static void CL_ParticleEffect_Fallback(int effectnameindex, float count, 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, qboolean wanttrail)
 {
        vec3_t center;
-       matrix4x4_t tempmatrix;
+       matrix4x4_t lightmatrix;
        particle_t *part;
+
        VectorLerp(originmins, 0.5, originmaxs, center);
-       Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
+       Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
        if (effectnameindex == EFFECT_SVC_PARTICLE)
        {
                if (cl_particles.integer)
                {
                        // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
                        if (count == 1024)
-                               CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                               CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        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);
+                               CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        else
                        {
                                count *= cl_particles_quality.value;
@@ -857,9 +959,9 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                }
        }
        else if (effectnameindex == EFFECT_TE_WIZSPIKE)
-               CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
+               CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
        else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
-               CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
+               CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
        else if (effectnameindex == EFFECT_TE_SPIKE)
        {
                if (cl_particles_bulletimpacts.integer)
@@ -867,7 +969,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        if (cl_particles_quake.integer)
                        {
                                if (cl_particles_smoke.integer)
-                                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                                       CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        }
                        else
                        {
@@ -887,7 +989,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        if (cl_particles_quake.integer)
                        {
                                if (cl_particles_smoke.integer)
-                                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                                       CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        }
                        else
                        {
@@ -899,7 +1001,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // bullet hole
                R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
        {
@@ -908,7 +1010,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        if (cl_particles_quake.integer)
                        {
                                if (cl_particles_smoke.integer)
-                                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                                       CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        }
                        else
                        {
@@ -928,7 +1030,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        if (cl_particles_quake.integer)
                        {
                                if (cl_particles_smoke.integer)
-                                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                                       CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        }
                        else
                        {
@@ -940,14 +1042,14 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // bullet hole
                R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_BLOOD)
        {
                if (!cl_particles_blood.integer)
                        return;
                if (cl_particles_quake.integer)
-                       CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
+                       CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                else
                {
                        static double bloodaccumulator = 0;
@@ -972,14 +1074,14 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // plasma scorch mark
                R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
                CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_GUNSHOT)
        {
                if (cl_particles_bulletimpacts.integer)
                {
                        if (cl_particles_quake.integer)
-                               CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                               CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        else
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
@@ -996,7 +1098,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                if (cl_particles_bulletimpacts.integer)
                {
                        if (cl_particles_quake.integer)
-                               CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+                               CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
                        else
                        {
                                CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
@@ -1007,17 +1109,17 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // bullet hole
                R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_EXPLOSION)
        {
                CL_ParticleExplosion(center);
-               CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
        {
                CL_ParticleExplosion(center);
-               CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
        {
@@ -1034,10 +1136,10 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                }
                else
                        CL_ParticleExplosion(center);
-               CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_SMALLFLASH)
-               CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        else if (effectnameindex == EFFECT_TE_FLAMEJET)
        {
                count *= cl_particles_quality.value;
@@ -1092,7 +1194,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                }
                if (!cl_particles_quake.integer)
                        CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
-               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);
+               CL_AllocLightFlash(NULL, &lightmatrix, 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(center, 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, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
@@ -1108,7 +1210,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
        else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
        {
                CL_ParticleExplosion(center);
-               CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               CL_AllocLightFlash(NULL, &lightmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
        {
@@ -1121,27 +1223,31 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                if (cl_particles_sparks.integer)
                        for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
                                CL_NewParticle(center, 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, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
-               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);
+               CL_AllocLightFlash(NULL, &lightmatrix, 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)
        {
+               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);
-               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);
+               CL_AllocLightFlash(NULL, &lightmatrix, 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)
        {
+               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);
-               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);
+               CL_AllocLightFlash(NULL, &lightmatrix, 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))
        {
                vec3_t dir, pos;
                float len, dec, qd;
-               int smoke, blood, bubbles, r, color;
+               int smoke, blood, bubbles, r, color, spawnedcount;
 
                if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
                {
@@ -1162,9 +1268,9 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
 
                        if (light[3])
                        {
-                               matrix4x4_t tempmatrix;
-                               Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
-                               R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                               matrix4x4_t traillightmatrix;
+                               Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
+                               R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &traillightmatrix, light, -1, NULL, true, 1, 0.25, 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++;
                        }
                }
@@ -1177,6 +1283,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
 
                VectorSubtract(originmaxs, originmins, dir);
                len = VectorNormalizeLength(dir);
+
                if (ent)
                {
                        dec = -ent->persistent.trail_time;
@@ -1198,8 +1305,9 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                blood = cl_particles.integer && cl_particles_blood.integer;
                bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
                qd = 1.0f / cl_particles_quality.value;
+               spawnedcount = 0;
 
-               while (len >= 0)
+               while (len >= 0 && ++spawnedcount <= 16384)
                {
                        dec = 3;
                        if (blood)
@@ -1350,11 +1458,10 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
 
 // this is also called on point effects with spawndlight = true and
 // 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];
        if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
        {
                Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
@@ -1370,6 +1477,11 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                vec3_t traildir;
                vec3_t trailpos;
                vec3_t rvec;
+               vec3_t angles;
+               vec3_t velocity;
+               vec3_t forward;
+               vec3_t right;
+               vec3_t up;
                vec_t traillen;
                vec_t trailstep;
                qboolean underwater;
@@ -1393,8 +1505,14 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                }
                for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
                {
-                       if (info->effectnameindex == effectnameindex)
+                       if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
                        {
+                               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;
@@ -1405,7 +1523,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                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]);
@@ -1413,7 +1531,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)
                                        {
@@ -1423,7 +1541,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("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++;
                                        }
                                }
@@ -1446,11 +1564,28 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                        staintex = min(staintex, info->staintex[1] - 1);
                                }
                                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])*avgtint[3], tex, info->color[0], info->color[1]);
+                               {
+                                       VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
+                                       AnglesFromVectors(angles, velocity, NULL, false);
+                                       AngleVectors(angles, forward, right, up);
+                                       VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
+
+                                       CL_SpawnDecalParticleForPoint(trailpos, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
+                               }
                                else if (info->orientation == PARTICLE_HBEAM)
-                                       CL_NewParticle(center, 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, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
+                               {
+                                       if (!drawastrail)
+                                               continue;
+
+                                       AnglesFromVectors(angles, traildir, NULL, false);
+                                       AngleVectors(angles, forward, right, up);
+                                       VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
+
+                                       CL_NewParticle(center, 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] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
+                               }
                                else
                                {
+                                       float cnt;
                                        if (!cl_particles.integer)
                                                continue;
                                        switch (info->particletype)
@@ -1463,22 +1598,50 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                        case pt_snow: if (!cl_particles_snow.integer) continue;break;
                                        default: break;
                                        }
-                                       VectorCopy(originmins, trailpos);
-                                       if (info->trailspacing > 0)
-                                       {
-                                               info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
-                                               trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
+
+                                       cnt = info->countabsolute;
+                                       cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
+                                       // if drawastrail is not set, we will
+                                       // use the regular cnt-based random
+                                       // particle spawning at the center; so
+                                       // do NOT apply trailspacing then!
+                                       if (drawastrail && definedastrail)
+                                               cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
+                                       cnt *= fade;
+                                       if (cnt == 0)
+                                               continue;  // nothing to draw
+                                       info->particleaccumulator += cnt;
+
+                                       if (drawastrail || definedastrail)
                                                immediatebloodstain = false;
-                                       }
                                        else
-                                       {
-                                               info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
-                                               trailstep = 0;
                                                immediatebloodstain =
                                                        ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
                                                        ||
                                                        ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
+
+                                       if (drawastrail)
+                                       {
+                                               VectorCopy(originmins, trailpos);
+                                               trailstep = traillen / cnt;
+                                       }
+                                       else
+                                       {
+                                               VectorCopy(center, trailpos);
+                                               trailstep = 0;
                                        }
+
+                                       if (trailstep == 0)
+                                       {
+                                               VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
+                                               AnglesFromVectors(angles, velocity, NULL, false);
+                                       }
+                                       else
+                                               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);
                                        info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
                                        for (;info->particleaccumulator >= 1;info->particleaccumulator--)
                                        {
@@ -1487,7 +1650,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                                        tex = (int)lhrandom(info->tex[0], info->tex[1]);
                                                        tex = min(tex, info->tex[1] - 1);
                                                }
-                                               if (!trailstep)
+                                               if (!(drawastrail || definedastrail))
                                                {
                                                        trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
                                                        trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
@@ -1499,7 +1662,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                                        Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
                                                }
                                                VectorRandom(rvec);
-                                               part = CL_NewParticle(center, 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, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
+                                               part = CL_NewParticle(center, 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] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
                                                if (immediatebloodstain && part)
                                                {
                                                        immediatebloodstain = false;
@@ -1513,12 +1676,23 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                }
        }
        if (!found)
-               CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
+               CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
 }
 
+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);
+}
+
+// note: this one ONLY does boxes!
 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);
 }
 
 /*
@@ -1528,8 +1702,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
@@ -1537,8 +1712,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++)
        {
@@ -1554,7 +1730,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];
@@ -1589,7 +1766,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)
@@ -1601,16 +1779,21 @@ 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);
+       if (c == 0)
+       {
+               return;
+       }
+
+       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);
 }
 
 /*
@@ -1625,11 +1808,11 @@ void CL_ParseParticleEffect (void)
        vec3_t org, dir;
        int i, count, msgcount, color;
 
-       MSG_ReadVector(org, cls.protocol);
+       MSG_ReadVector(&cl_message, org, cls.protocol);
        for (i=0 ; i<3 ; i++)
-               dir[i] = MSG_ReadChar () * (1.0 / 16.0);
-       msgcount = MSG_ReadByte ();
-       color = MSG_ReadByte ();
+               dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
+       msgcount = MSG_ReadByte(&cl_message);
+       color = MSG_ReadByte(&cl_message);
 
        if (msgcount == 255)
                count = 1024;
@@ -1693,7 +1876,7 @@ void CL_ParticleExplosion (const vec3_t org)
                                        {
                                                VectorRandom(v2);
                                                VectorMA(org, 128, v2, v);
-                                               trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
+                                               trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
                                        }
                                        while (k < 16 && trace.fraction < 0.1f);
                                        VectorSubtract(trace.endpos, org, v2);
@@ -1824,11 +2007,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_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"};
-static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
+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
@@ -1868,7 +2051,7 @@ static unsigned char shadebubble(float dx, float dy, vec3_t light)
 }
 
 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
-void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
+static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
 {
        *basex = (texnum % particlefontcols) * particlefontcellwidth;
        *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
@@ -1886,7 +2069,7 @@ static void setuptex(int texnum, unsigned char *data, unsigned char *particletex
                memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
 }
 
-void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
+static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
 {
        int x, y;
        float cx, cy, dx, dy, f, iradius;
@@ -1915,7 +2098,8 @@ void particletextureblotch(unsigned char *data, float radius, float red, float g
        }
 }
 
-void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
+#if 0
+static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
 {
        int i;
        for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
@@ -1925,8 +2109,9 @@ void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int
                data[2] = bound(minr, data[2], maxr);
        }
 }
+#endif
 
-void particletextureinvert(unsigned char *data)
+static void particletextureinvert(unsigned char *data)
 {
        int i;
        for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
@@ -2140,7 +2325,7 @@ static void R_InitParticleTexture (void)
                Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
 #endif
 
-               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
+               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
                particlefonttexture = decalskinframe->base;
 
                Mem_Free(particletexturedata);
@@ -2159,7 +2344,7 @@ static void R_InitParticleTexture (void)
        }
 
 #ifndef DUMPPARTICLEFONT
-       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, r_texture_convertsRGB_particles.integer != 0);
+       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
        {
@@ -2197,7 +2382,7 @@ static void R_InitParticleTexture (void)
                bufptr = buf;
                for(;;)
                {
-                       if(!COM_ParseToken_Simple(&bufptr, true, false))
+                       if(!COM_ParseToken_Simple(&bufptr, true, false, true))
                                break;
                        if(!strcmp(com_token, "\n"))
                                continue; // empty line
@@ -2209,22 +2394,22 @@ static void R_InitParticleTexture (void)
                        s2 = 1;
                        t2 = 1;
 
-                       if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
+                       if (COM_ParseToken_Simple(&bufptr, true, false, true) && 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"))
+                               if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
                                {
                                        texturename[0] = 0;
                                        t1 = atof(com_token);
-                                       if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
+                                       if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
                                        {
                                                s2 = atof(com_token);
-                                               if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
+                                               if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
                                                {
                                                        t2 = atof(com_token);
                                                        strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
-                                                       if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
+                                                       if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
                                                                strlcpy(texturename, com_token, sizeof(texturename));
                                                }
                                        }
@@ -2242,7 +2427,7 @@ static void R_InitParticleTexture (void)
                                Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
                                continue;
                        }
-                       sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true);
+                       sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
                        if(!sf)
                        {
                                // R_SkinFrame_LoadExternal already complained
@@ -2266,7 +2451,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)
@@ -2278,17 +2463,16 @@ static void r_part_newmap(void)
 {
        if (decalskinframe)
                R_SkinFrame_MarkUsed(decalskinframe);
-       CL_Particles_LoadEffectInfo();
+       CL_Particles_LoadEffectInfo(NULL);
 }
 
-#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;
@@ -2307,18 +2491,18 @@ void R_Particles_Init (void)
        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)
+static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
 {
        int surfacelistindex;
        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);
@@ -2375,7 +2559,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, false, true);
        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);
 }
@@ -2388,7 +2572,7 @@ void R_DrawDecals (void)
        float frametime;
        float decalfade;
        float drawdist2;
-       int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
+       unsigned int killsequence = cl.decalsequence - bound(0, (unsigned int) cl_decals_max.integer, cl.decalsequence);
 
        frametime = bound(0, cl.time - cl.decals_updatetime, 1);
        cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
@@ -2406,7 +2590,7 @@ void R_DrawDecals (void)
                if (!decal->typeindex)
                        continue;
 
-               if (killsequence - decal->decalsequence > 0)
+               if (killsequence > decal->decalsequence)
                        goto killdecal;
 
                if (cl.time > decal->time2 + cl_decals_time.value)
@@ -2434,7 +2618,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(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;
@@ -2455,11 +2639,12 @@ killdecal:
                Mem_Free(olddecals);
        }
 
-       r_refdef.stats.totaldecals = cl.num_decals;
+       r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
 }
 
-void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
+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;
@@ -2468,8 +2653,8 @@ 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 palpha, 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;
        vec4_t colormultiplier;
        float minparticledist_start, minparticledist_end;
        qboolean dofade;
@@ -2478,7 +2663,7 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
 
        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);
@@ -2538,10 +2723,10 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        // note: lighting is not cheap!
                        if (particletype[p->typeindex].lighting)
                        {
-                               R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
-                               c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
-                               c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
-                               c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
+                               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)
@@ -2603,7 +2788,10 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        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);
@@ -2690,7 +2878,7 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                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, false, false);
                }
 
                if (p->blendmode == PBLEND_INVMOD)
@@ -2732,7 +2920,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;
@@ -2797,7 +2985,7 @@ void R_DrawParticles (void)
 //                             if (p->bounce && cl.time >= p->delayedcollisions)
                                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_NORMAL, NULL, SUPERCONTENTS_SOLID | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, 0, collision_extendmovelength.value, 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
@@ -2821,7 +3009,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!
                                                                }
                                                        }
                                                }
@@ -2837,7 +3032,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;
@@ -2887,7 +3089,7 @@ void R_DrawParticles (void)
                                        break;
                                case pt_rain:
                                        a = CL_PointSuperContents(p->org);
-                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
+                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
                                                goto killparticle;
                                        break;
                                case pt_snow:
@@ -2899,7 +3101,7 @@ void R_DrawParticles (void)
                                                p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
                                        }
                                        a = CL_PointSuperContents(p->org);
-                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
+                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
                                                goto killparticle;
                                        break;
                                default:
@@ -2918,7 +3120,7 @@ void R_DrawParticles (void)
                {
                case pt_beam:
                        // beams have no culling
-                       R_MeshQueue_AddTransparent(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)
@@ -2932,7 +3134,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(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
+                               R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
                        break;
                }