]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
this patch may break things and needs testing
[xonotic/darkplaces.git] / cl_particles.c
index 435981a06968559eb7575857cf049b1f9fc2cc4f..d658b7b6e43bd8e425e25a418016101db6f4b7b2 100644 (file)
@@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include "cl_collision.h"
 #include "image.h"
+#include "r_shadow.h"
 
 // must match ptype_t values
 particletype_t particletype[pt_total] =
@@ -72,8 +73,8 @@ typedef struct particleeffectinfo_s
        // including 15)
        // if start and end of the range are the same, no randomization is done
        int tex[2];
-       // range of size values randomly chosen when spawning
-       float size[2];
+       // range of size values randomly chosen when spawning, plus size increase over time
+       float size[3];
        // range of alpha values randomly chosen when spawning, plus alpha fade
        float alpha[3];
        // how long the particle should live (note it is also removed if alpha drops to 0)
@@ -161,7 +162,7 @@ static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
-static const int tex_rainsplash[16] = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+static const int tex_rainsplash = 32;
 static const int tex_particle = 63;
 static const int tex_bubble = 62;
 static const int tex_raindrop = 61;
@@ -296,6 +297,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
                else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
                else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
+               else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
                else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
                else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
                else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
@@ -354,7 +356,6 @@ static const char *standardeffectnames[EFFECT_TOTAL] =
        "TE_SUPERSPIKEQUAD",
        "TE_WIZSPIKE",
        "TE_KNIGHTSPIKE",
-       "TE_VORESPIKE",
        "TE_EXPLOSION",
        "TE_EXPLOSIONQUAD",
        "TE_TAREXPLOSION",
@@ -450,7 +451,7 @@ void CL_Particles_Shutdown (void)
 // px,py,pz - starting origin of particle
 // pvx,pvy,pvz - starting velocity of particle
 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
-static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pfriction, float originjitter, float velocityjitter)
+static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
 {
        int l1, l2;
        particle_t *part;
@@ -471,6 +472,7 @@ static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int
        part->color[3] = 0xFF;
        part->texnum = ptex;
        part->size = psize;
+       part->sizeincrease = psizeincrease;
        part->alpha = palpha;
        part->alphafade = palphafade;
        part->gravity = pgravity;
@@ -483,7 +485,8 @@ static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int
        part->vel[1] = pvy + velocityjitter * v[1];
        part->vel[2] = pvz + velocityjitter * v[2];
        part->time2 = 0;
-       part->friction = pfriction;
+       part->airfriction = pairfriction;
+       part->liquidfriction = pliquidfriction;
        return part;
 }
 
@@ -492,15 +495,17 @@ void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t
        particle_t *p;
        if (!cl_decals.integer)
                return;
-       p = particle(particletype + pt_decal, color1, color2, texnum, size, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0);
+       p = particle(particletype + pt_decal, color1, color2, texnum, size, 0, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0, 0);
        if (p)
        {
                p->time2 = cl.time;
                p->owner = hitent;
                p->ownermodel = cl.entities[p->owner].render.model;
+               VectorAdd(org, normal, p->org);
+               VectorCopy(normal, p->vel);
+               // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
                Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
                Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
-               VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
        }
 }
 
@@ -516,7 +521,7 @@ void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size,
        {
                VectorRandom(org2);
                VectorMA(org, maxdist, org2, org2);
-               trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, false);
+               trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
                // 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))
@@ -532,7 +537,7 @@ 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, 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)
+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)
 {
        vec3_t center;
        matrix4x4_t tempmatrix;
@@ -554,11 +559,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                {
                                        int k = particlepalette[palettecolor + (rand()&7)];
                                        if (cl_particles_quake.integer)
-                                               particle(particletype + pt_alphastatic, k, k, tex_particle, 1, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 8, 0);
+                                               particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
                                        else if (gamemode == GAME_GOODVSBAD2)
-                                               particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 8, 10);
+                                               particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
                                        else
-                                               particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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, 8, 15);
+                                               particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
                                }
                        }
                }
@@ -598,7 +603,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // bullet hole
                if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocDlight(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, &tempmatrix, 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)
        {
@@ -631,7 +636,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // bullet hole
                if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocDlight(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, &tempmatrix, 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)
        {
@@ -644,7 +649,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        static double bloodaccumulator = 0;
                        bloodaccumulator += count * 0.333 * cl_particles_quality.value;
                        for (;bloodaccumulator > 0;bloodaccumulator--)
-                               particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 64);
+                               particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
                }
        }
        else if (effectnameindex == EFFECT_TE_SPARK)
@@ -654,7 +659,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // plasma scorch mark
                if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
                CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocDlight(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, &tempmatrix, 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)
        {
@@ -681,17 +686,17 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                // bullet hole
                if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
                CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
-               CL_AllocDlight(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, &tempmatrix, 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_AllocDlight(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, &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);
        }
        else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
        {
                CL_ParticleExplosion(center);
-               CL_AllocDlight(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, &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);
        }
        else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
        {
@@ -701,22 +706,22 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                        for (i = 0;i < 1024 * cl_particles_quality.value;i++)
                        {
                                if (i & 1)
-                                       particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, 16, 256);
+                                       particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
                                else
-                                       particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 16, 0);
+                                       particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
                        }
                }
                else
                        CL_ParticleExplosion(center);
-               CL_AllocDlight(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, &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);
        }
        else if (effectnameindex == EFFECT_TE_SMALLFLASH)
-               CL_AllocDlight(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, &tempmatrix, 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;
                while (count-- > 0)
-                       particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 128);
+                       particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
        }
        else if (effectnameindex == EFFECT_TE_LAVASPLASH)
        {
@@ -735,7 +740,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                org[1] = center[1] + dir[1];
                                org[2] = center[2] + lhrandom(0, 64);
                                vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
-                               particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0);
+                               particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
                        }
                }
        }
@@ -744,7 +749,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                float i, j, k, inc, vel;
                vec3_t dir;
 
-               inc = 4 / cl_particles_quality.value;
+               inc = 8 / cl_particles_quality.value;
                for (i = -16;i < 16;i += inc)
                {
                        for (j = -16;j < 16;j += inc)
@@ -754,27 +759,28 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        VectorSet(dir, i*8, j*8, k*8);
                                        VectorNormalize(dir);
                                        vel = lhrandom(50, 113);
-                                       particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0);
+                                       particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
                                }
                        }
                }
-               CL_AllocDlight(NULL, &tempmatrix, 200, 1.0f, 1.0f, 1.0f, 600, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+               particle(particletype + pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
+               CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_TE_TEI_G3)
-               particle(particletype + pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0);
+               particle(particletype + 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);
        else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
        {
                if (cl_particles_smoke.integer)
                {
                        count *= 0.25f * cl_particles_quality.value;
                        while (count-- > 0)
-                               particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 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, 1.5f, 6.0f);
+                               particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
                }
        }
        else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
        {
                CL_ParticleExplosion(center);
-               CL_AllocDlight(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, &tempmatrix, 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)
        {
@@ -784,25 +790,25 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
                if (cl_particles_smoke.integer)
                        for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
-                               particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 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, 20, 155);
+                               particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
                if (cl_particles_sparks.integer)
                        for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
-                               particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 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, 465);
-               CL_AllocDlight(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);
+                               particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
+               CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_EF_FLAME)
        {
                count *= 300 * cl_particles_quality.value;
                while (count-- > 0)
-                       particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 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, 16, 128);
-               CL_AllocDlight(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);
+                       particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
+               CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (effectnameindex == EFFECT_EF_STARDUST)
        {
                count *= 200 * cl_particles_quality.value;
                while (count-- > 0)
-                       particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 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, 16, 128);
-               CL_AllocDlight(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);
+                       particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
+               CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        }
        else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
        {
@@ -810,17 +816,33 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                float len, dec, qd;
                int smoke, blood, bubbles, r, color;
 
-               if (effectnameindex == EFFECT_TR_ROCKET)
-                       CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 3.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_TR_VORESPIKE)
+               if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
                {
-                       if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
-                               CL_AllocDlight(&ent->render, &ent->render.matrix, 100, 0.3f, 0.6f, 1.2f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
-                       else
-                               CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 1.2f, 0.5f, 1.0f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                       vec4_t light;
+                       Vector4Set(light, 0, 0, 0, 0);
+
+                       if (effectnameindex == EFFECT_TR_ROCKET)
+                               Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
+                       else if (effectnameindex == EFFECT_TR_VORESPIKE)
+                       {
+                               if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
+                                       Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
+                               else
+                                       Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
+                       }
+                       else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
+                               Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
+
+                       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.lights[r_refdef.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                       }
                }
-               else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
-                       CL_AllocDlight(&ent->render, &ent->render.matrix, 200, 0.75f, 1.5f, 3.0f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+
+               if (!spawnparticles)
+                       return;
 
                if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
                        return;
@@ -854,12 +876,12 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        if (cl_particles_quake.integer)
                                        {
                                                color = particlepalette[67 + (rand()&3)];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
                                        }
                                        else
                                        {
                                                dec = 16;
-                                               particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 64);
+                                               particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
@@ -868,12 +890,12 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                dec = 6;
                                                color = particlepalette[67 + (rand()&3)];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
                                        }
                                        else
                                        {
                                                dec = 32;
-                                               particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 0, 64);
+                                               particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
                                        }
                                }
                        }
@@ -885,12 +907,12 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                r = rand()&3;
                                                color = particlepalette[ramp3[r]];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
                                        }
                                        else
                                        {
-                                               particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
-                                               particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 20);
+                                               particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_GRENADE)
@@ -899,11 +921,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                r = 2 + (rand()%5);
                                                color = particlepalette[ramp3[r]];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
                                        }
                                        else
                                        {
-                                               particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_WIZSPIKE)
@@ -912,18 +934,18 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                dec = 6;
                                                color = particlepalette[52 + (rand()&7)];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
                                        }
                                        else if (gamemode == GAME_GOODVSBAD2)
                                        {
                                                dec = 6;
-                                               particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                        }
                                        else
                                        {
                                                color = particlepalette[20 + (rand()&7)];
-                                               particle(particletype + pt_static, color, color, tex_particle, 2, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
@@ -932,13 +954,13 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        {
                                                dec = 6;
                                                color = particlepalette[230 + (rand()&7)];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0);
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
                                        }
                                        else
                                        {
                                                color = particlepalette[226 + (rand()&7)];
-                                               particle(particletype + pt_static, color, color, tex_particle, 2, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                        }
                                }
                                else if (effectnameindex == EFFECT_TR_VORESPIKE)
@@ -946,40 +968,40 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                        if (cl_particles_quake.integer)
                                        {
                                                color = particlepalette[152 + (rand()&3)];
-                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 8, 0);
+                                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
                                        }
                                        else if (gamemode == GAME_GOODVSBAD2)
                                        {
                                                dec = 6;
-                                               particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                        }
                                        else if (gamemode == GAME_PRYDON)
                                        {
                                                dec = 6;
-                                               particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                        }
                                        else
-                                               particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                               particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                                }
                                else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
                                {
                                        dec = 7;
-                                       particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 4);
+                                       particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
                                }
                                else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
                                {
                                        dec = 4;
-                                       particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 16);
+                                       particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
                                }
                                else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
-                                       particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
+                                       particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
                        }
                        if (bubbles)
                        {
                                if (effectnameindex == EFFECT_TR_ROCKET)
-                                       particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, (1.0 / 16.0), 0, 16);
+                                       particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
                                else if (effectnameindex == EFFECT_TR_GRENADE)
-                                       particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, (1.0 / 16.0), 0, 16);
+                                       particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
                        }
                        // advance to next time and position
                        dec *= qd;
@@ -992,7 +1014,11 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
 }
 
-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)
+// 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)
 {
        vec3_t center;
        qboolean found = false;
@@ -1012,6 +1038,8 @@ void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmin
                vec3_t traildir;
                vec3_t trailpos;
                vec3_t rvec;
+               vec_t traillen;
+               vec_t trailstep;
                qboolean underwater;
                // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
                VectorLerp(originmins, 0.5, originmaxs, center);
@@ -1019,6 +1047,7 @@ void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmin
                supercontents = CL_PointSuperContents(center);
                underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
                VectorSubtract(originmaxs, originmins, traildir);
+               traillen = VectorLength(traildir);
                VectorNormalize(traildir);
                for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
                {
@@ -1031,16 +1060,31 @@ void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmin
                                        continue;
 
                                // spawn a dlight if requested
-                               if (info->lightradiusstart > 0)
+                               if (info->lightradiusstart > 0 && spawndlight)
                                {
                                        matrix4x4_t tempmatrix;
                                        if (info->trailspacing > 0)
                                                Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
                                        else
                                                Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
-                                       CL_AllocDlight(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                                       if (info->lighttime > 0 && info->lightradiusfade > 0)
+                                       {
+                                               // light flash (explosion, etc)
+                                               // called when effect starts
+                                               CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                                       }
+                                       else
+                                       {
+                                               // glowing entity
+                                               // called by CL_LinkNetworkEntity
+                                               Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
+                                               R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+                                       }
                                }
 
+                               if (!spawnparticles)
+                                       continue;
+
                                // spawn particles
                                tex = info->tex[0];
                                if (info->tex[1] > info->tex[0])
@@ -1051,7 +1095,7 @@ void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmin
                                if (info->particletype == pt_decal)
                                        CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
                                else if (info->particletype == pt_beam)
-                                       particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), 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);
+                                       particle(particletype + 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);
                                else
                                {
                                        if (!cl_particles.integer)
@@ -1064,32 +1108,47 @@ void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmin
                                        case pt_blood: if (!cl_particles_blood.integer) continue;break;
                                        default: break;
                                        }
-                                       info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
                                        VectorCopy(originmins, trailpos);
-                                       for (;info->particleaccumulator > 0;info->particleaccumulator--)
+                                       if (info->trailspacing > 0)
+                                       {
+                                               info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
+                                               trailstep = info->trailspacing / cl_particles_quality.value;
+                                       }
+                                       else
+                                       {
+                                               info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
+                                               trailstep = 0;
+                                       }
+                                       info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
+                                       for (;info->particleaccumulator >= 1;info->particleaccumulator--)
                                        {
                                                if (info->tex[1] > info->tex[0])
                                                {
                                                        tex = (int)lhrandom(info->tex[0], info->tex[1]);
                                                        tex = min(tex, info->tex[1] - 1);
                                                }
-                                               if (info->trailspacing <= 0)
+                                               if (!trailstep)
                                                {
                                                        trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
                                                        trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
                                                        trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
                                                }
                                                VectorRandom(rvec);
-                                               particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), 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, 0, 0);
-                                               if (info->trailspacing > 0)
-                                                       VectorMA(trailpos, info->trailspacing, traildir, trailpos);
+                                               particle(particletype + 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);
+                                               if (trailstep)
+                                                       VectorMA(trailpos, trailstep, traildir, trailpos);
                                        }
                                }
                        }
                }
        }
        if (!found)
-               CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor);
+               CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
+}
+
+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);
 }
 
 /*
@@ -1117,7 +1176,7 @@ void CL_EntityParticles (const entity_t *ent)
                v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
                v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
                v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
-               particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0);
+               particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0);
        }
 }
 
@@ -1169,16 +1228,16 @@ void CL_ReadPointFile_f (void)
                if (cl.num_particles < cl.max_particles - 3)
                {
                        s++;
-                       particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0);
+                       particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0);
                }
        }
        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]);
 
-       particle(particletype + pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0);
-       particle(particletype + pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0);
-       particle(particletype + pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0);
+       particle(particletype + 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);
+       particle(particletype + 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);
+       particle(particletype + 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);
 }
 
 /*
@@ -1232,12 +1291,12 @@ void CL_ParticleExplosion (const vec3_t org)
                        if (i & 1)
                        {
                                color = particlepalette[ramp1[r]];
-                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 16, 256);
+                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
                        }
                        else
                        {
                                color = particlepalette[ramp2[r]];
-                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 16, 256);
+                               particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
                        }
                }
        }
@@ -1248,7 +1307,7 @@ void CL_ParticleExplosion (const vec3_t org)
                {
                        if (cl_particles.integer && cl_particles_bubbles.integer)
                                for (i = 0;i < 128 * cl_particles_quality.value;i++)
-                                       particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, (1.0 / 16.0), 16, 96);
+                                       particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96);
                }
                else
                {
@@ -1265,19 +1324,19 @@ void CL_ParticleExplosion (const vec3_t org)
                                                v[0] = org[0] + lhrandom(-48, 48);
                                                v[1] = org[1] + lhrandom(-48, 48);
                                                v[2] = org[2] + lhrandom(-48, 48);
-                                               trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
+                                               trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
                                                if (trace.fraction >= 0.1)
                                                        break;
                                        }
                                        VectorSubtract(trace.endpos, org, v2);
                                        VectorScale(v2, 2.0f, v2);
-                                       particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0);
+                                       particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 0, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
                                }
                        }
 
                        if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
                                for (i = 0;i < 128 * cl_particles_quality.value;i++)
-                                       particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0, 256);
+                                       particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0.8, 0, 256);
                }
        }
 
@@ -1300,9 +1359,9 @@ void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
        {
                k = particlepalette[colorStart + (i % colorLength)];
                if (cl_particles_quake.integer)
-                       particle(particletype + pt_static, k, k, tex_particle, 1, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, 8, 256);
+                       particle(particletype + pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
                else
-                       particle(particletype + pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), 8, 192);
+                       particle(particletype + pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192);
        }
 }
 
@@ -1312,13 +1371,13 @@ static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const ve
        {
                sparkcount *= cl_particles_quality.value;
                while(sparkcount-- > 0)
-                       particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + sv_gravity.value * 0.1, 0, 0, 64);
+                       particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + sv_gravity.value * 0.1, 0, 0, 0, 64);
        }
        if (cl_particles_smoke.integer)
        {
                smokecount *= cl_particles_quality.value;
                while(smokecount-- > 0)
-                       particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 255, 1024, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8);
+                       particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 0, 255, 1024, 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, 8);
        }
 }
 
@@ -1331,7 +1390,7 @@ void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
        while (count--)
        {
                k = particlepalette[colorbase + (rand()&3)];
-               particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, randomvel);
+               particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel);
        }
 }
 
@@ -1362,9 +1421,9 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
                {
                        k = particlepalette[colorbase + (rand()&3)];
                        if (gamemode == GAME_GOODVSBAD2)
-                               particle(particletype + pt_rain, k, k, tex_particle, 20, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
+                               particle(particletype + pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
                        else
-                               particle(particletype + pt_rain, k, k, tex_particle, 0.5, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
+                               particle(particletype + pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
                }
                break;
        case 1:
@@ -1372,9 +1431,9 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
                {
                        k = particlepalette[colorbase + (rand()&3)];
                        if (gamemode == GAME_GOODVSBAD2)
-                               p = particle(particletype + pt_snow, k, k, tex_particle, 20, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
+                               p = particle(particletype + pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
                        else
-                               p = particle(particletype + pt_snow, k, k, tex_particle, 1, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0);
+                               p = particle(particletype + pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
                        if (p)
                                VectorCopy(p->vel, p->relativedirection);
                }
@@ -1393,7 +1452,8 @@ void CL_MoveParticles (void)
 {
        particle_t *p;
        int i, maxparticle, j, a, content;
-       float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
+       float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
+       particletype_t *decaltype, *bloodtype;
        int hitent;
        trace_t trace;
 
@@ -1404,18 +1464,57 @@ void CL_MoveParticles (void)
                return;
        }
 
-       frametime = cl.time - cl.oldtime;
+       frametime = bound(0, cl.time - cl.oldtime, 0.1);
        gravity = frametime * sv_gravity.value;
        dvel = 1+4*frametime;
-       bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
+       decalfade = frametime * 255 / cl_decals_fadetime.value;
+       decaltype = particletype + pt_decal;
+       bloodtype = particletype + pt_blood;
 
        maxparticle = -1;
        j = 0;
        for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
        {
                if (!p->type)
+               {
+                       if (cl.free_particle > i)
+                               cl.free_particle = i;
                        continue;
+               }
                maxparticle = i;
+
+               // heavily optimized decal case
+               if (p->type == decaltype)
+               {
+                       // FIXME: this has fairly wacky handling of alpha
+                       if (cl.time > p->time2 + cl_decals_time.value)
+                       {
+                               p->alpha -= decalfade;
+                               if (p->alpha <= 0)
+                               {
+                                       p->type = NULL;
+                                       if (cl.free_particle > i)
+                                               cl.free_particle = i;
+                                       continue;
+                               }
+                       }
+                       if (p->owner)
+                       {
+                               if (cl.entities[p->owner].render.model == p->ownermodel)
+                               {
+                                       Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
+                                       Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
+                               }
+                               else
+                               {
+                                       p->type = NULL;
+                                       if (cl.free_particle > i)
+                                               cl.free_particle = i;
+                               }
+                       }
+                       continue;
+               }
+
                content = 0;
 
                p->alpha -= p->alphafade * frametime;
@@ -1423,6 +1522,8 @@ void CL_MoveParticles (void)
                if (p->alpha <= 0)
                {
                        p->type = NULL;
+                       if (cl.free_particle > i)
+                               cl.free_particle = i;
                        continue;
                }
 
@@ -1433,7 +1534,7 @@ void CL_MoveParticles (void)
                        VectorCopy(p->org, org);
                        if (p->bounce)
                        {
-                               trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), false);
+                               trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, 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
@@ -1454,18 +1555,20 @@ void CL_MoveParticles (void)
                                                VectorCopy(trace.plane.normal, p->vel);
                                                VectorAdd(p->org, p->vel, p->org);
                                                p->type = particletype + pt_raindecal;
-                                               p->texnum = tex_rainsplash[0];
+                                               p->texnum = tex_rainsplash;
                                                p->time2 = cl.time;
                                                p->alphafade = p->alpha / 0.4;
                                                p->bounce = 0;
-                                               p->friction = 0;
+                                               p->airfriction = 0;
+                                               p->liquidfriction = 0;
                                                p->gravity = 0;
-                                               p->size = 8.0;
+                                               p->size *= 1.0f;
+                                               p->sizeincrease = p->size * 16;
                                                count = rand() & 3;
                                                while(count--)
-                                                       particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 32);
+                                                       particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, 32 + p->vel[2]*16, 0, 0, 0, 32);
                                        }
-                                       else if (p->type == particletype + pt_blood)
+                                       else if (p->type == bloodtype)
                                        {
                                                // blood - splash on solid
                                                if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
@@ -1488,12 +1591,14 @@ void CL_MoveParticles (void)
                                                p->texnum = tex_blooddecal[rand()&7];
                                                p->owner = hitent;
                                                p->ownermodel = cl.entities[hitent].render.model;
+                                               // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
                                                Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
                                                Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
                                                p->time2 = cl.time;
                                                p->alphafade = 0;
                                                p->bounce = 0;
-                                               p->friction = 0;
+                                               p->airfriction = 0;
+                                               p->liquidfriction = 0;
                                                p->gravity = 0;
                                                p->size *= 2.0f;
                                        }
@@ -1515,12 +1620,14 @@ void CL_MoveParticles (void)
                        }
                        p->vel[2] -= p->gravity * gravity;
 
-                       if (p->friction)
+                       if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
+                       {
+                               f = 1.0f - min(p->liquidfriction * frametime, 1);
+                               VectorScale(p->vel, f, p->vel);
+                       }
+                       else if (p->airfriction)
                        {
-                               f = p->friction * frametime;
-                               if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
-                                       f *= 4;
-                               f = 1.0f - f;
+                               f = 1.0f - min(p->airfriction * frametime, 1);
                                VectorScale(p->vel, f, p->vel);
                        }
                }
@@ -1574,34 +1681,12 @@ void CL_MoveParticles (void)
                                if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
                                        p->type = NULL;
                                break;
-                       case pt_smoke:
-                               //p->size += frametime * 15;
-                               break;
-                       case pt_decal:
-                               // FIXME: this has fairly wacky handling of alpha
-                               p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
-                               if (cl.entities[p->owner].render.model == p->ownermodel)
-                               {
-                                       Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
-                                       Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
-                               }
-                               else
-                                       p->type = NULL;
-                               break;
-                       case pt_raindecal:
-                               a = (int)max(0, (cl.time - p->time2) * 40);
-                               if (a < 16)
-                                       p->texnum = tex_rainsplash[a];
-                               else
-                                       p->type = NULL;
-                               break;
                        default:
                                break;
                        }
                }
        }
        cl.num_particles = maxparticle + 1;
-       cl.free_particle = 0;
 }
 
 #define MAX_PARTICLETEXTURES 64
@@ -1660,10 +1745,6 @@ static void setuptex(int texnum, unsigned char *data, unsigned char *particletex
        int basex, basey, y;
        basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
        basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
-       particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
-       particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
-       particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
-       particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
        for (y = 0;y < PARTICLETEXTURESIZE;y++)
                memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
 }
@@ -1749,13 +1830,14 @@ static void R_InitBloodTextures (unsigned char *particletexturedata)
 
 }
 
+//uncomment this to make engine save out particle font to a tga file when run
+//#define DUMPPARTICLEFONT
+
 static void R_InitParticleTexture (void)
 {
        int x, y, d, i, k, m;
-       float dx, dy, radius, f, f2;
-       unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
+       float dx, dy, f;
        vec3_t light;
-       unsigned char *particletexturedata;
 
        // a note: decals need to modulate (multiply) the background color to
        // properly darken it (stain), and they need to be able to alpha fade,
@@ -1766,166 +1848,179 @@ static void R_InitParticleTexture (void)
        // and white on black background) so we can alpha fade it to black, then
        // we invert it again during the blendfunc to make it work...
 
-       particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
-       memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
-
-       // smoke
-       for (i = 0;i < 8;i++)
+#ifndef DUMPPARTICLEFONT
+       particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
+       if (!particlefonttexture)
+#endif
        {
-               memset(&data[0][0][0], 255, sizeof(data));
-               do
-               {
-                       unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
+               unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
+               unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
+               memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
 
-                       fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
-                       fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
-                       m = 0;
-                       for (y = 0;y < PARTICLETEXTURESIZE;y++)
+               // smoke
+               for (i = 0;i < 8;i++)
+               {
+                       memset(&data[0][0][0], 255, sizeof(data));
+                       do
                        {
-                               dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                               for (x = 0;x < PARTICLETEXTURESIZE;x++)
+                               unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
+
+                               fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
+                               fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
+                               m = 0;
+                               for (y = 0;y < PARTICLETEXTURESIZE;y++)
                                {
-                                       dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                                       d = (noise2[y][x] - 128) * 3 + 192;
-                                       if (d > 0)
-                                               d = (int)(d * (1-(dx*dx+dy*dy)));
-                                       d = (d * noise1[y][x]) >> 7;
-                                       d = bound(0, d, 255);
-                                       data[y][x][3] = (unsigned char) d;
-                                       if (m < d)
-                                               m = d;
+                                       dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                                       for (x = 0;x < PARTICLETEXTURESIZE;x++)
+                                       {
+                                               dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                                               d = (noise2[y][x] - 128) * 3 + 192;
+                                               if (d > 0)
+                                                       d = (int)(d * (1-(dx*dx+dy*dy)));
+                                               d = (d * noise1[y][x]) >> 7;
+                                               d = bound(0, d, 255);
+                                               data[y][x][3] = (unsigned char) d;
+                                               if (m < d)
+                                                       m = d;
+                                       }
                                }
                        }
+                       while (m < 224);
+                       setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
                }
-               while (m < 224);
-               setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
-       }
 
-       // rain splash
-       for (i = 0;i < 16;i++)
-       {
+               // rain splash
                memset(&data[0][0][0], 255, sizeof(data));
-               radius = i * 3.0f / 4.0f / 16.0f;
-               f2 = 255.0f * ((15.0f - i) / 15.0f);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
                        dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
                        for (x = 0;x < PARTICLETEXTURESIZE;x++)
                        {
                                dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                               f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
+                               f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
                                data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
                        }
                }
-               setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
-       }
+               setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
 
-       // normal particle
-       memset(&data[0][0][0], 255, sizeof(data));
-       for (y = 0;y < PARTICLETEXTURESIZE;y++)
-       {
-               dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-               for (x = 0;x < PARTICLETEXTURESIZE;x++)
+               // normal particle
+               memset(&data[0][0][0], 255, sizeof(data));
+               for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
-                       dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                       d = (int)(256 * (1 - (dx*dx+dy*dy)));
-                       d = bound(0, d, 255);
-                       data[y][x][3] = (unsigned char) d;
+                       dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                       for (x = 0;x < PARTICLETEXTURESIZE;x++)
+                       {
+                               dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                               d = (int)(256 * (1 - (dx*dx+dy*dy)));
+                               d = bound(0, d, 255);
+                               data[y][x][3] = (unsigned char) d;
+                       }
                }
-       }
-       setuptex(tex_particle, &data[0][0][0], particletexturedata);
+               setuptex(tex_particle, &data[0][0][0], particletexturedata);
 
-       // rain
-       memset(&data[0][0][0], 255, sizeof(data));
-       light[0] = 1;light[1] = 1;light[2] = 1;
-       VectorNormalize(light);
-       for (y = 0;y < PARTICLETEXTURESIZE;y++)
-       {
-               dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-               // stretch upper half of bubble by +50% and shrink lower half by -50%
-               // (this gives an elongated teardrop shape)
-               if (dy > 0.5f)
-                       dy = (dy - 0.5f) * 2.0f;
-               else
-                       dy = (dy - 0.5f) / 1.5f;
-               for (x = 0;x < PARTICLETEXTURESIZE;x++)
+               // rain
+               memset(&data[0][0][0], 255, sizeof(data));
+               light[0] = 1;light[1] = 1;light[2] = 1;
+               VectorNormalize(light);
+               for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
-                       dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                       // shrink bubble width to half
-                       dx *= 2.0f;
-                       data[y][x][3] = shadebubble(dx, dy, light);
+                       dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                       // stretch upper half of bubble by +50% and shrink lower half by -50%
+                       // (this gives an elongated teardrop shape)
+                       if (dy > 0.5f)
+                               dy = (dy - 0.5f) * 2.0f;
+                       else
+                               dy = (dy - 0.5f) / 1.5f;
+                       for (x = 0;x < PARTICLETEXTURESIZE;x++)
+                       {
+                               dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                               // shrink bubble width to half
+                               dx *= 2.0f;
+                               data[y][x][3] = shadebubble(dx, dy, light);
+                       }
                }
-       }
-       setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
+               setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
 
-       // bubble
-       memset(&data[0][0][0], 255, sizeof(data));
-       light[0] = 1;light[1] = 1;light[2] = 1;
-       VectorNormalize(light);
-       for (y = 0;y < PARTICLETEXTURESIZE;y++)
-       {
-               dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-               for (x = 0;x < PARTICLETEXTURESIZE;x++)
+               // bubble
+               memset(&data[0][0][0], 255, sizeof(data));
+               light[0] = 1;light[1] = 1;light[2] = 1;
+               VectorNormalize(light);
+               for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
-                       dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                       data[y][x][3] = shadebubble(dx, dy, light);
+                       dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                       for (x = 0;x < PARTICLETEXTURESIZE;x++)
+                       {
+                               dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
+                               data[y][x][3] = shadebubble(dx, dy, light);
+                       }
                }
-       }
-       setuptex(tex_bubble, &data[0][0][0], particletexturedata);
+               setuptex(tex_bubble, &data[0][0][0], particletexturedata);
 
-       // Blood particles and blood decals
-       R_InitBloodTextures (particletexturedata);
+               // Blood particles and blood decals
+               R_InitBloodTextures (particletexturedata);
 
-       // bullet decals
-       for (i = 0;i < 8;i++)
-       {
-               memset(&data[0][0][0], 255, sizeof(data));
-               for (k = 0;k < 12;k++)
-                       particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
-               for (k = 0;k < 3;k++)
-                       particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
-               //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
-               particletextureinvert(&data[0][0][0]);
-               setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
-       }
+               // bullet decals
+               for (i = 0;i < 8;i++)
+               {
+                       memset(&data[0][0][0], 255, sizeof(data));
+                       for (k = 0;k < 12;k++)
+                               particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
+                       for (k = 0;k < 3;k++)
+                               particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
+                       //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
+                       particletextureinvert(&data[0][0][0]);
+                       setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
+               }
 
-#if 0
-       Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
+#ifdef DUMPPARTICLEFONT
+               Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
 #endif
 
-       particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
-       if (!particlefonttexture)
                particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
+
+               Mem_Free(particletexturedata);
+       }
        for (i = 0;i < MAX_PARTICLETEXTURES;i++)
+       {
+               int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
+               int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
                particletexture[i].texture = particlefonttexture;
+               particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
+               particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
+               particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
+               particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
+       }
 
-       // nexbeam
-       fractalnoise(&noise3[0][0], 64, 4);
-       m = 0;
-       for (y = 0;y < 64;y++)
+#ifndef DUMPPARTICLEFONT
+       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
+       if (!particletexture[tex_beam].texture)
+#endif
        {
-               dy = (y - 0.5f*64) / (64*0.5f-1);
-               for (x = 0;x < 16;x++)
+               unsigned char noise3[64][64], data2[64][16][4];
+               // nexbeam
+               fractalnoise(&noise3[0][0], 64, 4);
+               m = 0;
+               for (y = 0;y < 64;y++)
                {
-                       dx = (x - 0.5f*16) / (16*0.5f-2);
-                       d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
-                       data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
-                       data2[y][x][3] = 255;
+                       dy = (y - 0.5f*64) / (64*0.5f-1);
+                       for (x = 0;x < 16;x++)
+                       {
+                               dx = (x - 0.5f*16) / (16*0.5f-2);
+                               d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
+                               data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
+                               data2[y][x][3] = 255;
+                       }
                }
-       }
 
-#if 0
-       Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
+#ifdef DUMPPARTICLEFONT
+               Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
 #endif
-
-       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
-       if (!particletexture[tex_beam].texture)
                particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
+       }
        particletexture[tex_beam].s1 = 0;
        particletexture[tex_beam].t1 = 0;
        particletexture[tex_beam].s2 = 1;
        particletexture[tex_beam].t2 = 1;
-       Mem_Free(particletexturedata);
 }
 
 static void r_part_start(void)
@@ -1944,89 +2039,126 @@ static void r_part_newmap(void)
 {
 }
 
+#define BATCHSIZE 256
+int particle_element3i[BATCHSIZE*6];
+float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
+
 void R_Particles_Init (void)
 {
+       int i;
+       for (i = 0;i < BATCHSIZE;i++)
+       {
+               particle_element3i[i*6+0] = i*4+0;
+               particle_element3i[i*6+1] = i*4+1;
+               particle_element3i[i*6+2] = i*4+2;
+               particle_element3i[i*6+3] = i*4+0;
+               particle_element3i[i*6+4] = i*4+2;
+               particle_element3i[i*6+5] = i*4+3;
+       }
+
        Cvar_RegisterVariable(&r_drawparticles);
        R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
 }
 
-float particle_vertex3f[12], particle_texcoord2f[8];
-
-void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
+void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
 {
-       const particle_t *p = cl.particles + surfacenumber;
-       rmeshstate_t m;
+       int surfacelistindex;
+       int batchstart, batchcount;
+       const particle_t *p;
        pblend_t blendmode;
-       float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
-       particletexture_t *tex;
-
-       VectorCopy(p->org, org);
-
-       blendmode = p->type->blendmode;
-       tex = &particletexture[p->texnum];
-       cr = p->color[0] * (1.0f / 255.0f);
-       cg = p->color[1] * (1.0f / 255.0f);
-       cb = p->color[2] * (1.0f / 255.0f);
-       ca = p->alpha * (1.0f / 255.0f);
-       if (blendmode == PBLEND_MOD)
-       {
-               cr *= ca;
-               cg *= ca;
-               cb *= ca;
-               cr = min(cr, 1);
-               cg = min(cg, 1);
-               cb = min(cb, 1);
-               ca = 1;
-       }
-       ca /= cl_particles_quality.value;
-       if (p->type->lighting)
-       {
-               float ambient[3], diffuse[3], diffusenormal[3];
-               R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
-               cr *= (ambient[0] + 0.5 * diffuse[0]);
-               cg *= (ambient[1] + 0.5 * diffuse[1]);
-               cb *= (ambient[2] + 0.5 * diffuse[2]);
-       }
-       if (fogenabled)
-       {
-               fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
-               ifog = 1 - fog;
-               cr = cr * ifog;
-               cg = cg * ifog;
-               cb = cb * ifog;
-               if (blendmode == PBLEND_ALPHA)
-               {
-                       cr += fogcolor[0] * fog;
-                       cg += fogcolor[1] * fog;
-                       cb += fogcolor[2] * fog;
-               }
-       }
+       rtexture_t *texture;
+       float *v3f, *t2f, *c4f;
 
        R_Mesh_Matrix(&identitymatrix);
-
-       memset(&m, 0, sizeof(m));
-       m.tex[0] = R_GetTexture(tex->texture);
-       m.pointer_texcoord[0] = particle_texcoord2f;
-       m.pointer_vertex = particle_vertex3f;
-       R_Mesh_State(&m);
-
-       GL_Color(cr, cg, cb, ca);
-
-       if (blendmode == PBLEND_ALPHA)
-               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-       else if (blendmode == PBLEND_ADD)
-               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
-       else //if (blendmode == PBLEND_MOD)
-               GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+       R_Mesh_ResetTextureState();
+       R_Mesh_VertexPointer(particle_vertex3f);
+       R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
+       R_Mesh_ColorPointer(particle_color4f);
        GL_DepthMask(false);
        GL_DepthTest(true);
-       size = p->size * cl_particles_size.value;
-       if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
+       GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
+
+       // first generate all the vertices at once
+       for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
        {
-               if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
+               particletexture_t *tex;
+               const float *org;
+               float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
+
+               p = cl.particles + surfacelist[surfacelistindex];
+
+               blendmode = p->type->blendmode;
+
+               cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
+               cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
+               cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
+               ca = p->alpha * (1.0f / 255.0f);
+               if (blendmode == PBLEND_MOD)
+               {
+                       cr *= ca;
+                       cg *= ca;
+                       cb *= ca;
+                       cr = min(cr, 1);
+                       cg = min(cg, 1);
+                       cb = min(cb, 1);
+                       ca = 1;
+               }
+               ca /= cl_particles_quality.value;
+               if (p->type->lighting)
+               {
+                       float ambient[3], diffuse[3], diffusenormal[3];
+                       R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
+                       cr *= (ambient[0] + 0.5 * diffuse[0]);
+                       cg *= (ambient[1] + 0.5 * diffuse[1]);
+                       cb *= (ambient[2] + 0.5 * diffuse[2]);
+               }
+               if (r_refdef.fogenabled)
+               {
+                       fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
+                       ifog = 1 - fog;
+                       cr = cr * ifog;
+                       cg = cg * ifog;
+                       cb = cb * ifog;
+                       if (blendmode == PBLEND_ALPHA)
+                       {
+                               cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
+                               cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
+                               cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
+                       }
+               }
+               c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
+               c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
+               c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
+               c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
+
+               size = p->size * cl_particles_size.value;
+               org = p->org;
+               tex = &particletexture[p->texnum];
+               if (p->type->orientation == PARTICLE_BILLBOARD)
+               {
+                       VectorScale(r_view.left, -size, right);
+                       VectorScale(r_view.up, size, up);
+                       v3f[ 0] = org[0] - right[0] - up[0];
+                       v3f[ 1] = org[1] - right[1] - up[1];
+                       v3f[ 2] = org[2] - right[2] - up[2];
+                       v3f[ 3] = org[0] - right[0] + up[0];
+                       v3f[ 4] = org[1] - right[1] + up[1];
+                       v3f[ 5] = org[2] - right[2] + up[2];
+                       v3f[ 6] = org[0] + right[0] + up[0];
+                       v3f[ 7] = org[1] + right[1] + up[1];
+                       v3f[ 8] = org[2] + right[2] + up[2];
+                       v3f[ 9] = org[0] + right[0] - up[0];
+                       v3f[10] = org[1] + right[1] - up[1];
+                       v3f[11] = org[2] + right[2] - up[2];
+                       t2f[0] = tex->s1;t2f[1] = tex->t2;
+                       t2f[2] = tex->s1;t2f[3] = tex->t1;
+                       t2f[4] = tex->s2;t2f[5] = tex->t1;
+                       t2f[6] = tex->s2;t2f[7] = tex->t2;
+               }
+               else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
                {
                        // double-sided
-                       if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
+                       if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
                        {
                                VectorNegate(p->vel, v);
                                VectorVectors(v, right, up);
@@ -2035,58 +2167,93 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacen
                                VectorVectors(p->vel, right, up);
                        VectorScale(right, size, right);
                        VectorScale(up, size, up);
+                       v3f[ 0] = org[0] - right[0] - up[0];
+                       v3f[ 1] = org[1] - right[1] - up[1];
+                       v3f[ 2] = org[2] - right[2] - up[2];
+                       v3f[ 3] = org[0] - right[0] + up[0];
+                       v3f[ 4] = org[1] - right[1] + up[1];
+                       v3f[ 5] = org[2] - right[2] + up[2];
+                       v3f[ 6] = org[0] + right[0] + up[0];
+                       v3f[ 7] = org[1] + right[1] + up[1];
+                       v3f[ 8] = org[2] + right[2] + up[2];
+                       v3f[ 9] = org[0] + right[0] - up[0];
+                       v3f[10] = org[1] + right[1] - up[1];
+                       v3f[11] = org[2] + right[2] - up[2];
+                       t2f[0] = tex->s1;t2f[1] = tex->t2;
+                       t2f[2] = tex->s1;t2f[3] = tex->t1;
+                       t2f[4] = tex->s2;t2f[5] = tex->t1;
+                       t2f[6] = tex->s2;t2f[7] = tex->t2;
+               }
+               else if (p->type->orientation == PARTICLE_SPARK)
+               {
+                       VectorMA(org, -0.02, p->vel, v);
+                       VectorMA(org, 0.02, p->vel, up2);
+                       R_CalcBeam_Vertex3f(v3f, v, up2, size);
+                       t2f[0] = tex->s1;t2f[1] = tex->t2;
+                       t2f[2] = tex->s1;t2f[3] = tex->t1;
+                       t2f[4] = tex->s2;t2f[5] = tex->t1;
+                       t2f[6] = tex->s2;t2f[7] = tex->t2;
+               }
+               else if (p->type->orientation == PARTICLE_BEAM)
+               {
+                       R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
+                       VectorSubtract(p->vel, org, up);
+                       VectorNormalize(up);
+                       v[0] = DotProduct(org, up) * (1.0f / 64.0f);
+                       v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
+                       t2f[0] = 1;t2f[1] = v[0];
+                       t2f[2] = 0;t2f[3] = v[0];
+                       t2f[4] = 0;t2f[5] = v[1];
+                       t2f[6] = 1;t2f[7] = v[1];
                }
                else
                {
-                       VectorScale(r_viewleft, -size, right);
-                       VectorScale(r_viewup, size, up);
+                       Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
+                       return;
                }
-               particle_vertex3f[ 0] = org[0] - right[0] - up[0];
-               particle_vertex3f[ 1] = org[1] - right[1] - up[1];
-               particle_vertex3f[ 2] = org[2] - right[2] - up[2];
-               particle_vertex3f[ 3] = org[0] - right[0] + up[0];
-               particle_vertex3f[ 4] = org[1] - right[1] + up[1];
-               particle_vertex3f[ 5] = org[2] - right[2] + up[2];
-               particle_vertex3f[ 6] = org[0] + right[0] + up[0];
-               particle_vertex3f[ 7] = org[1] + right[1] + up[1];
-               particle_vertex3f[ 8] = org[2] + right[2] + up[2];
-               particle_vertex3f[ 9] = org[0] + right[0] - up[0];
-               particle_vertex3f[10] = org[1] + right[1] - up[1];
-               particle_vertex3f[11] = org[2] + right[2] - up[2];
-               particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
-               particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
-               particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
-               particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
-       }
-       else if (p->type->orientation == PARTICLE_SPARK)
-       {
-               VectorMA(p->org, -0.02, p->vel, v);
-               VectorMA(p->org, 0.02, p->vel, up2);
-               R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
-               particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
-               particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
-               particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
-               particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
        }
-       else if (p->type->orientation == PARTICLE_BEAM)
-       {
-               R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
-               VectorSubtract(p->vel, p->org, up);
-               VectorNormalize(up);
-               v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
-               v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
-               particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
-               particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
-               particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
-               particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
-       }
-       else
+
+       // now render batches of particles based on blendmode and texture
+       blendmode = PBLEND_ADD;
+       GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
+       texture = particletexture[63].texture;
+       R_Mesh_TexBind(0, R_GetTexture(texture));
+       GL_LockArrays(0, numsurfaces*4);
+       batchstart = 0;
+       batchcount = 0;
+       for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
        {
-               Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
-               return;
-       }
+               p = cl.particles + surfacelist[surfacelistindex];
+
+               if (blendmode != p->type->blendmode)
+               {
+                       if (batchcount > 0)
+                               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
+                       batchcount = 0;
+                       batchstart = surfacelistindex;
+                       blendmode = p->type->blendmode;
+                       if (blendmode == PBLEND_ALPHA)
+                               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+                       else if (blendmode == PBLEND_ADD)
+                               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
+                       else //if (blendmode == PBLEND_MOD)
+                               GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+               }
+               if (texture != particletexture[p->texnum].texture)
+               {
+                       if (batchcount > 0)
+                               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
+                       batchcount = 0;
+                       batchstart = surfacelistindex;
+                       texture = particletexture[p->texnum].texture;
+                       R_Mesh_TexBind(0, R_GetTexture(texture));
+               }
 
-       R_Mesh_Draw(0, 4, 2, polygonelements);
+               batchcount++;
+       }
+       if (batchcount > 0)
+               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
+       GL_LockArrays(0, 0);
 }
 
 void R_DrawParticles (void)
@@ -2099,21 +2266,16 @@ void R_DrawParticles (void)
        if ((!cl.num_particles) || (!r_drawparticles.integer))
                return;
 
-       minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
+       minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
 
        // LordHavoc: only render if not too close
        for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
        {
                if (p->type)
                {
-                       renderstats.particles++;
-                       if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
-                       {
-                               if (p->type == particletype + pt_decal)
-                                       R_DrawParticle_TransparentCallback(0, i, 0);
-                               else
-                                       R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
-                       }
+                       r_refdef.stats.particles++;
+                       if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
+                               R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
                }
        }
 }