]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
optimized pose math to not use intermediate matrix
[xonotic/darkplaces.git] / cl_particles.c
index 4621674452b3231b9fc2f4f78f654cb2c1bffd2f..5229124a170cdbb7343fd97d250c3c7ec7398100 100644 (file)
@@ -24,9 +24,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "image.h"
 #include "r_shadow.h"
 
-#define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
-#define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
-
 // must match ptype_t values
 particletype_t particletype[pt_total] =
 {
@@ -120,10 +117,8 @@ typedef struct particleeffectinfo_s
 }
 particleeffectinfo_t;
 
-#define MAX_PARTICLEEFFECTNAME 256
 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
 
-#define MAX_PARTICLEEFFECTINFO 4096
 
 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
 
@@ -169,7 +164,6 @@ int         ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
 
 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
 
-#define MAX_PARTICLETEXTURES 1024
 // particletexture_t is a rectangle in the particlefonttexture
 typedef struct particletexture_s
 {
@@ -181,6 +175,7 @@ particletexture_t;
 static rtexturepool_t *particletexturepool;
 static rtexture_t *particlefonttexture;
 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
+skinframe_t *decalskinframe;
 
 // texture numbers in particle font
 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
@@ -216,9 +211,14 @@ cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes,
 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
+cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
+cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
+cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
+cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
+cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
 
 
-void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
+void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
 {
        int arrayindex;
        int argc;
@@ -247,7 +247,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                }
                if (argc < 1)
                        continue;
-#define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
+#define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
@@ -260,7 +260,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                        effectinfoindex++;
                        if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
                        {
-                               Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
+                               Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
                                break;
                        }
                        for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
@@ -279,7 +279,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                        // if we run out of names, abort
                        if (effectnameindex == MAX_PARTICLEEFFECTNAME)
                        {
-                               Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
+                               Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
                                break;
                        }
                        info = particleeffectinfo + effectinfoindex;
@@ -309,7 +309,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                }
                else if (info == NULL)
                {
-                       Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
+                       Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
                        break;
                }
                else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
@@ -329,7 +329,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                        else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
                        else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
                        else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
-                       else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
+                       else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
                        info->blendmode = particletype[info->particletype].blendmode;
                        info->orientation = particletype[info->particletype].orientation;
                }
@@ -339,7 +339,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                        if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
                        else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
                        else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
-                       else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
+                       else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
                }
                else if (!strcmp(argv[0], "orientation"))
                {
@@ -348,7 +348,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                        else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
                        else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
                        else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
-                       else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
+                       else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
                }
                else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
                else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
@@ -379,7 +379,7 @@ void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
                else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
                else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
                else
-                       Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
+                       Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
 #undef checkparms
 #undef readints
 #undef readfloats
@@ -448,16 +448,26 @@ static const char *standardeffectnames[EFFECT_TOTAL] =
 void CL_Particles_LoadEffectInfo(void)
 {
        int i;
+       int filepass;
        unsigned char *filedata;
        fs_offset_t filesize;
+       char filename[MAX_QPATH];
        memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
        memset(particleeffectname, 0, sizeof(particleeffectname));
        for (i = 0;i < EFFECT_TOTAL;i++)
                strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
-       filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
-       if (filedata)
+       for (filepass = 0;;filepass++)
        {
-               CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
+               if (filepass == 0)
+                       dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
+               else if (filepass == 1)
+                       dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
+               else
+                       break;
+               filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
+               if (!filedata)
+                       continue;
+               CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
                Mem_Free(filedata);
        }
 }
@@ -471,7 +481,7 @@ void CL_ReadPointFile_f (void);
 void CL_Particles_Init (void)
 {
        Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
-       Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
+       Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
 
        Cvar_RegisterVariable (&cl_particles);
        Cvar_RegisterVariable (&cl_particles_quality);
@@ -496,12 +506,20 @@ void CL_Particles_Init (void)
        Cvar_RegisterVariable (&cl_decals_visculling);
        Cvar_RegisterVariable (&cl_decals_time);
        Cvar_RegisterVariable (&cl_decals_fadetime);
+       Cvar_RegisterVariable (&cl_decals_newsystem);
+       Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
+       Cvar_RegisterVariable (&cl_decals_models);
+       Cvar_RegisterVariable (&cl_decals_bias);
+       Cvar_RegisterVariable (&cl_decals_max);
 }
 
 void CL_Particles_Shutdown (void)
 {
 }
 
+void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
+void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
+
 // list of all 26 parameters:
 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
@@ -640,15 +658,57 @@ particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2,
                trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
                part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
        }
+
        return part;
 }
 
+static void CL_ImmediateBloodStain(particle_t *part)
+{
+       vec3_t v;
+       int staintex;
+
+       // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
+       if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
+       {
+               VectorCopy(part->vel, v);
+               VectorNormalize(v);
+               staintex = part->staintexnum;
+               R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
+       }
+
+       // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
+       if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
+       {
+               VectorCopy(part->vel, v);
+               VectorNormalize(v);
+               staintex = tex_blooddecal[rand()&7];
+               R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
+       }
+}
+
 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
 {
        int l1, l2;
        decal_t *decal;
+       entity_render_t *ent = &cl.entities[hitent].render;
+       unsigned char color[3];
        if (!cl_decals.integer)
                return;
+       if (!ent->allowdecals)
+               return;
+
+       l2 = (int)lhrandom(0.5, 256.5);
+       l1 = 256 - l2;
+       color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
+       color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
+       color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
+
+       if (cl_decals_newsystem.integer)
+       {
+               R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
+               return;
+       }
+
        for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
        if (cl.free_decal >= cl.max_decals)
                return;
@@ -656,18 +716,17 @@ void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t
        if (cl.num_decals < cl.free_decal)
                cl.num_decals = cl.free_decal;
        memset(decal, 0, sizeof(*decal));
+       decal->decalsequence = cl.decalsequence++;
        decal->typeindex = pt_decal;
        decal->texnum = texnum;
-       VectorAdd(org, normal, decal->org);
+       VectorMA(org, cl_decals_bias.value, normal, decal->org);
        VectorCopy(normal, decal->normal);
        decal->size = size;
        decal->alpha = alpha;
        decal->time2 = cl.time;
-       l2 = (int)lhrandom(0.5, 256.5);
-       l1 = 256 - l2;
-       decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
-       decal->color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
-       decal->color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
+       decal->color[0] = color[0];
+       decal->color[1] = color[1];
+       decal->color[2] = color[2];
        decal->owner = hitent;
        decal->clusterindex = -1000; // no vis culling unless we're sure
        if (hitent)
@@ -721,6 +780,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
 {
        vec3_t center;
        matrix4x4_t tempmatrix;
+       particle_t *part;
        VectorLerp(originmins, 0.5, originmaxs, center);
        Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
        if (effectnameindex == EFFECT_SVC_PARTICLE)
@@ -838,10 +898,18 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                else
                {
                        static double bloodaccumulator = 0;
+                       qboolean immediatebloodstain = true;
                        //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
                        bloodaccumulator += count * 0.333 * cl_particles_quality.value;
                        for (;bloodaccumulator > 0;bloodaccumulator--)
-                               CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 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, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
+                       {
+                               part = CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 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, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
+                               if (immediatebloodstain && part)
+                               {
+                                       immediatebloodstain = false;
+                                       CL_ImmediateBloodStain(part);
+                               }
+                       }
                }
        }
        else if (effectnameindex == EFFECT_TE_SPARK)
@@ -1044,7 +1112,7 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                                matrix4x4_t tempmatrix;
                                Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
                                R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
-                               r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
+                               r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
                        }
                }
 
@@ -1223,8 +1291,8 @@ void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t o
                if (ent)
                        ent->persistent.trail_time = len;
        }
-       else if (developer.integer >= 1)
-               Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
+       else
+               Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
 }
 
 // this is also called on point effects with spawndlight = true and
@@ -1255,6 +1323,8 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                vec_t traillen;
                vec_t trailstep;
                qboolean underwater;
+               qboolean immediatebloodstain;
+               particle_t *part;
                // 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);
                VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
@@ -1293,7 +1363,7 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                                // called by CL_LinkNetworkEntity
                                                Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
                                                R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.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);
-                                               r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
+                                               r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
                                        }
                                }
 
@@ -1337,11 +1407,13 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                        {
                                                info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
                                                trailstep = info->trailspacing / cl_particles_quality.value;
+                                               immediatebloodstain = false;
                                        }
                                        else
                                        {
                                                info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
                                                trailstep = 0;
+                                               immediatebloodstain = info->particletype == pt_blood || staintex;
                                        }
                                        info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
                                        for (;info->particleaccumulator >= 1;info->particleaccumulator--)
@@ -1358,7 +1430,12 @@ void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins
                                                        trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
                                                }
                                                VectorRandom(rvec);
-                                               CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
+                                               part = CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
+                                               if (immediatebloodstain && part)
+                                               {
+                                                       immediatebloodstain = false;
+                                                       CL_ImmediateBloodStain(part);
+                                               }
                                                if (trailstep)
                                                        VectorMA(trailpos, trailstep, traildir, trailpos);
                                        }
@@ -1786,32 +1863,34 @@ void particletextureinvert(unsigned char *data)
 static void R_InitBloodTextures (unsigned char *particletexturedata)
 {
        int i, j, k, m;
-       unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
+       size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
+       unsigned char *data = Mem_Alloc(tempmempool, datasize);
 
        // blood particles
        for (i = 0;i < 8;i++)
        {
-               memset(&data[0][0][0], 255, sizeof(data));
+               memset(data, 255, datasize);
                for (k = 0;k < 24;k++)
-                       particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
-               //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
-               particletextureinvert(&data[0][0][0]);
-               setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
+                       particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
+               //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
+               particletextureinvert(data);
+               setuptex(tex_bloodparticle[i], data, particletexturedata);
        }
 
        // blood decals
        for (i = 0;i < 8;i++)
        {
-               memset(&data[0][0][0], 255, sizeof(data));
+               memset(data, 255, datasize);
                m = 8;
                for (j = 1;j < 10;j++)
                        for (k = min(j, m - 1);k < m;k++)
-                               particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
-               //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
-               particletextureinvert(&data[0][0][0]);
-               setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
+                               particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
+               //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
+               particletextureinvert(data);
+               setuptex(tex_blooddecal[i], data, particletexturedata);
        }
 
+       Mem_Free(data);
 }
 
 //uncomment this to make engine save out particle font to a tga file when run
@@ -1836,9 +1915,10 @@ static void R_InitParticleTexture (void)
        // we invert it again during the blendfunc to make it work...
 
 #ifndef DUMPPARTICLEFONT
-       particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
-       if (particlefonttexture)
+       decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
+       if (decalskinframe)
        {
+               particlefonttexture = decalskinframe->base;
                // TODO maybe allow custom grid size?
                particlefontwidth = image_width;
                particlefontheight = image_height;
@@ -1851,7 +1931,10 @@ static void R_InitParticleTexture (void)
 #endif
        {
                unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
-               unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
+               size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
+               unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
+               unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
+               unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
 
                particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
                particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
@@ -1863,13 +1946,11 @@ static void R_InitParticleTexture (void)
                // smoke
                for (i = 0;i < 8;i++)
                {
-                       memset(&data[0][0][0], 255, sizeof(data));
+                       memset(data, 255, datasize);
                        do
                        {
-                               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);
+                               fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
+                               fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
                                m = 0;
                                for (y = 0;y < PARTICLETEXTURESIZE;y++)
                                {
@@ -1877,23 +1958,23 @@ static void R_InitParticleTexture (void)
                                        for (x = 0;x < PARTICLETEXTURESIZE;x++)
                                        {
                                                dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                                               d = (noise2[y][x] - 128) * 3 + 192;
+                                               d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
                                                if (d > 0)
                                                        d = (int)(d * (1-(dx*dx+dy*dy)));
-                                               d = (d * noise1[y][x]) >> 7;
+                                               d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
                                                d = bound(0, d, 255);
-                                               data[y][x][3] = (unsigned char) d;
+                                               data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
                                                if (m < d)
                                                        m = d;
                                        }
                                }
                        }
                        while (m < 224);
-                       setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
+                       setuptex(tex_smoke[i], data, particletexturedata);
                }
 
                // rain splash
-               memset(&data[0][0][0], 255, sizeof(data));
+               memset(data, 255, datasize);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
                        dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
@@ -1901,13 +1982,13 @@ static void R_InitParticleTexture (void)
                        {
                                dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
                                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));
+                               data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
                        }
                }
-               setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
+               setuptex(tex_rainsplash, data, particletexturedata);
 
                // normal particle
-               memset(&data[0][0][0], 255, sizeof(data));
+               memset(data, 255, datasize);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
                        dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
@@ -1916,13 +1997,13 @@ static void R_InitParticleTexture (void)
                                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;
+                               data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
                        }
                }
-               setuptex(tex_particle, &data[0][0][0], particletexturedata);
+               setuptex(tex_particle, data, particletexturedata);
 
                // rain
-               memset(&data[0][0][0], 255, sizeof(data));
+               memset(data, 255, datasize);
                light[0] = 1;light[1] = 1;light[2] = 1;
                VectorNormalize(light);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
@@ -1939,13 +2020,13 @@ static void R_InitParticleTexture (void)
                                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);
+                               data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
                        }
                }
-               setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
+               setuptex(tex_raindrop, data, particletexturedata);
 
                // bubble
-               memset(&data[0][0][0], 255, sizeof(data));
+               memset(data, 255, datasize);
                light[0] = 1;light[1] = 1;light[2] = 1;
                VectorNormalize(light);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
@@ -1954,10 +2035,10 @@ static void R_InitParticleTexture (void)
                        for (x = 0;x < PARTICLETEXTURESIZE;x++)
                        {
                                dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
-                               data[y][x][3] = shadebubble(dx, dy, light);
+                               data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
                        }
                }
-               setuptex(tex_bubble, &data[0][0][0], particletexturedata);
+               setuptex(tex_bubble, data, particletexturedata);
 
                // Blood particles and blood decals
                R_InitBloodTextures (particletexturedata);
@@ -1965,23 +2046,27 @@ static void R_InitParticleTexture (void)
                // bullet decals
                for (i = 0;i < 8;i++)
                {
-                       memset(&data[0][0][0], 255, sizeof(data));
+                       memset(data, 255, datasize);
                        for (k = 0;k < 12;k++)
-                               particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
+                               particletextureblotch(data, 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);
+                               particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
+                       //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
+                       particletextureinvert(data);
+                       setuptex(tex_bulletdecal[i], data, particletexturedata);
                }
 
 #ifdef DUMPPARTICLEFONT
                Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
 #endif
 
-               particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
+               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
+               particlefonttexture = decalskinframe->base;
 
                Mem_Free(particletexturedata);
+               Mem_Free(data);
+               Mem_Free(noise1);
+               Mem_Free(noise2);
        }
        for (i = 0;i < MAX_PARTICLETEXTURES;i++)
        {
@@ -1994,7 +2079,7 @@ static void R_InitParticleTexture (void)
        }
 
 #ifndef DUMPPARTICLEFONT
-       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
+       particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true);
        if (!particletexture[tex_beam].texture)
 #endif
        {
@@ -2017,7 +2102,7 @@ static void R_InitParticleTexture (void)
 #ifdef DUMPPARTICLEFONT
                Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
 #endif
-               particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
+               particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
        }
        particletexture[tex_beam].s1 = 0;
        particletexture[tex_beam].t1 = 0;
@@ -2097,11 +2182,14 @@ static void r_part_shutdown(void)
 
 static void r_part_newmap(void)
 {
+       if (decalskinframe)
+               R_SkinFrame_MarkUsed(decalskinframe);
        CL_Particles_LoadEffectInfo();
 }
 
 #define BATCHSIZE 256
 unsigned short particle_elements[BATCHSIZE*6];
+float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
 
 void R_Particles_Init (void)
 {
@@ -2130,19 +2218,16 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        float *v3f, *t2f, *c4f;
        particletexture_t *tex;
        float right[3], up[3], size, ca;
-       float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
+       float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
        float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
 
-       // set up global fogging in worldspace (RSurf_FogVertex depends on this)
-       VectorCopy(r_refdef.view.origin, rsurface.localvieworigin);
+       RSurf_ActiveWorldEntity();
 
-       r_refdef.stats.decals += numsurfaces;
-       R_Mesh_Matrix(&identitymatrix);
+       r_refdef.stats.drawndecals += numsurfaces;
        R_Mesh_ResetTextureState();
        R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
        R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
        R_Mesh_ColorPointer(particle_color4f, 0, 0);
-       R_SetupGenericShader(true);
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
        GL_PolygonOffset(0, 0);
@@ -2195,10 +2280,8 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        // now render the decals all at once
        // (this assumes they all use one particle font texture!)
        GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
-       R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
-       GL_LockArrays(0, numsurfaces*4);
+       R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
        R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
-       GL_LockArrays(0, 0);
 }
 
 void R_DrawDecals (void)
@@ -2209,6 +2292,7 @@ void R_DrawDecals (void)
        float frametime;
        float decalfade;
        float drawdist2;
+       int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
 
        frametime = bound(0, cl.time - cl.decals_updatetime, 1);
        cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
@@ -2226,6 +2310,9 @@ void R_DrawDecals (void)
                if (!decal->typeindex)
                        continue;
 
+               if (killsequence - decal->decalsequence > 0)
+                       goto killdecal;
+
                if (cl.time > decal->time2 + cl_decals_time.value)
                {
                        decal->alpha -= decalfade;
@@ -2263,14 +2350,16 @@ killdecal:
        while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
                cl.num_decals--;
 
-       if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
+       if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
        {
                decal_t *olddecals = cl.decals;
-               cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
+               cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
                cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
                memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
                Mem_Free(olddecals);
        }
+
+       r_refdef.stats.totaldecals = cl.num_decals;
 }
 
 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
@@ -2285,20 +2374,16 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
        float ambient[3], diffuse[3], diffusenormal[3];
        vec4_t colormultiplier;
-       float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
 
-       // set up global fogging in worldspace (RSurf_FogVertex depends on this)
-       VectorCopy(r_refdef.view.origin, rsurface.localvieworigin);
+       RSurf_ActiveWorldEntity();
 
        Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
 
        r_refdef.stats.particles += numsurfaces;
-       R_Mesh_Matrix(&identitymatrix);
        R_Mesh_ResetTextureState();
        R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
        R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
        R_Mesh_ColorPointer(particle_color4f, 0, 0);
-       R_SetupGenericShader(true);
        GL_DepthMask(false);
        GL_DepthRange(0, 1);
        GL_PolygonOffset(0, 0);
@@ -2312,15 +2397,28 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
 
                blendmode = p->blendmode;
 
-               c4f[0] = p->color[0] * colormultiplier[0];
-               c4f[1] = p->color[1] * colormultiplier[1];
-               c4f[2] = p->color[2] * colormultiplier[2];
-               c4f[3] = p->alpha * colormultiplier[3];
                switch (blendmode)
                {
                case PBLEND_INVALID:
                case PBLEND_INVMOD:
+                       c4f[0] = p->color[0] * (1.0f / 256.0f);
+                       c4f[1] = p->color[1] * (1.0f / 256.0f);
+                       c4f[2] = p->color[2] * (1.0f / 256.0f);
+                       c4f[3] = p->alpha * colormultiplier[3];
+                       // additive and modulate can just fade out in fog (this is correct)
+                       if (r_refdef.fogenabled)
+                               c4f[3] *= RSurf_FogVertex(p->org);
+                       // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
+                       c4f[0] *= c4f[3];
+                       c4f[1] *= c4f[3];
+                       c4f[2] *= c4f[3];
+                       c4f[3] = 1;
+                       break;
                case PBLEND_ADD:
+                       c4f[0] = p->color[0] * colormultiplier[0];
+                       c4f[1] = p->color[1] * colormultiplier[1];
+                       c4f[2] = p->color[2] * colormultiplier[2];
+                       c4f[3] = p->alpha * colormultiplier[3];
                        // additive and modulate can just fade out in fog (this is correct)
                        if (r_refdef.fogenabled)
                                c4f[3] *= RSurf_FogVertex(p->org);
@@ -2331,6 +2429,10 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        c4f[3] = 1;
                        break;
                case PBLEND_ALPHA:
+                       c4f[0] = p->color[0] * colormultiplier[0];
+                       c4f[1] = p->color[1] * colormultiplier[1];
+                       c4f[2] = p->color[2] * colormultiplier[2];
+                       c4f[3] = p->alpha * colormultiplier[3];
                        // note: lighting is not cheap!
                        if (particletype[p->typeindex].lighting)
                        {
@@ -2443,7 +2545,6 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        // now render batches of particles based on blendmode and texture
        blendmode = PBLEND_INVALID;
        texture = NULL;
-       GL_LockArrays(0, numsurfaces*4);
        batchstart = 0;
        batchcount = 0;
        for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
@@ -2470,7 +2571,7 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                if (texture != particletexture[p->texnum].texture)
                {
                        texture = particletexture[p->texnum].texture;
-                       R_Mesh_TexBind(0, R_GetTexture(texture));
+                       R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
                }
 
                // iterate until we find a change in settings
@@ -2485,7 +2586,6 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                batchcount = surfacelistindex - batchstart;
                R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
        }
-       GL_LockArrays(0, 0);
 }
 
 void R_DrawParticles (void)
@@ -2707,10 +2807,10 @@ killparticle:
        while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
                cl.num_particles--;
 
-       if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
+       if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
        {
                particle_t *oldparticles = cl.particles;
-               cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
+               cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
                cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
                memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
                Mem_Free(oldparticles);