]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_particles.c
implemented music playlist system with 10 playlists, automatic track
[xonotic/darkplaces.git] / cl_particles.c
index f607fab881c35f2c52ec2d63f56f73d8c0bbd0cb..eb850f069b819035f41a02db29ff704b9118ee21 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] =
 {
@@ -197,7 +194,10 @@ cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies o
 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
-cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
+cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
+cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
+cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
+cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
@@ -221,7 +221,7 @@ cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias
 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;
@@ -250,7 +250,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)
@@ -263,7 +263,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++)
@@ -282,7 +282,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;
@@ -312,7 +312,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);}
@@ -332,7 +332,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;
                }
@@ -342,7 +342,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"))
                {
@@ -351,7 +351,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);}
@@ -382,7 +382,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
@@ -451,16 +451,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);
        }
 }
@@ -474,7 +484,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);
@@ -483,6 +493,9 @@ void CL_Particles_Init (void)
        Cvar_RegisterVariable (&cl_particles_quake);
        Cvar_RegisterVariable (&cl_particles_blood);
        Cvar_RegisterVariable (&cl_particles_blood_alpha);
+       Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
+       Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
+       Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
        Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
        Cvar_RegisterVariable (&cl_particles_explosions_sparks);
        Cvar_RegisterVariable (&cl_particles_explosions_shell);
@@ -1105,7 +1118,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++;
                        }
                }
 
@@ -1284,8 +1297,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
@@ -1356,7 +1369,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++;
                                        }
                                }
 
@@ -1856,12 +1869,13 @@ void particletextureinvert(unsigned char *data)
 static void R_InitBloodTextures (unsigned char *particletexturedata)
 {
        int i, j, k, m;
-       unsigned char *data = Mem_Alloc(tempmempool, 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, 255, sizeof(data));
+               memset(data, 255, datasize);
                for (k = 0;k < 24;k++)
                        particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
                //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
@@ -1872,7 +1886,7 @@ static void R_InitBloodTextures (unsigned char *particletexturedata)
        // blood decals
        for (i = 0;i < 8;i++)
        {
-               memset(data, 255, sizeof(data));
+               memset(data, 255, datasize);
                m = 8;
                for (j = 1;j < 10;j++)
                        for (k = min(j, m - 1);k < m;k++)
@@ -1907,7 +1921,7 @@ static void R_InitParticleTexture (void)
        // we invert it again during the blendfunc to make it work...
 
 #ifndef DUMPPARTICLEFONT
-       decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
+       decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
        if (decalskinframe)
        {
                particlefonttexture = decalskinframe->base;
@@ -1923,7 +1937,8 @@ static void R_InitParticleTexture (void)
 #endif
        {
                unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
-               unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, 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);
 
@@ -1937,7 +1952,7 @@ static void R_InitParticleTexture (void)
                // smoke
                for (i = 0;i < 8;i++)
                {
-                       memset(data, 255, sizeof(data));
+                       memset(data, 255, datasize);
                        do
                        {
                                fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
@@ -1965,7 +1980,7 @@ static void R_InitParticleTexture (void)
                }
 
                // rain splash
-               memset(data, 255, sizeof(data));
+               memset(data, 255, datasize);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
                        dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
@@ -1979,7 +1994,7 @@ static void R_InitParticleTexture (void)
                setuptex(tex_rainsplash, data, particletexturedata);
 
                // normal particle
-               memset(data, 255, sizeof(data));
+               memset(data, 255, datasize);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
                {
                        dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
@@ -1994,7 +2009,7 @@ static void R_InitParticleTexture (void)
                setuptex(tex_particle, data, particletexturedata);
 
                // rain
-               memset(data, 255, sizeof(data));
+               memset(data, 255, datasize);
                light[0] = 1;light[1] = 1;light[2] = 1;
                VectorNormalize(light);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
@@ -2017,7 +2032,7 @@ static void R_InitParticleTexture (void)
                setuptex(tex_raindrop, data, particletexturedata);
 
                // bubble
-               memset(data, 255, sizeof(data));
+               memset(data, 255, datasize);
                light[0] = 1;light[1] = 1;light[2] = 1;
                VectorNormalize(light);
                for (y = 0;y < PARTICLETEXTURESIZE;y++)
@@ -2037,7 +2052,7 @@ static void R_InitParticleTexture (void)
                // bullet decals
                for (i = 0;i < 8;i++)
                {
-                       memset(data, 255, sizeof(data));
+                       memset(data, 255, datasize);
                        for (k = 0;k < 12;k++)
                                particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
                        for (k = 0;k < 3;k++)
@@ -2051,7 +2066,7 @@ static void R_InitParticleTexture (void)
                Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
 #endif
 
-               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
+               decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
                particlefonttexture = decalskinframe->base;
 
                Mem_Free(particletexturedata);
@@ -2070,7 +2085,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
        {
@@ -2093,7 +2108,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;
@@ -2209,7 +2224,7 @@ 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];
 
        RSurf_ActiveWorldEntity();
@@ -2219,7 +2234,6 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        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);
@@ -2272,10 +2286,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)
@@ -2344,10 +2356,10 @@ 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);
@@ -2378,7 +2390,6 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        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);
@@ -2392,15 +2403,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);
@@ -2411,6 +2435,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)
                        {
@@ -2523,7 +2551,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;)
@@ -2550,7 +2577,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
@@ -2565,7 +2592,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)
@@ -2682,7 +2708,7 @@ void R_DrawParticles (void)
                                                                if (cl_decals.integer)
                                                                {
                                                                        // create a decal for the blood splat
-                                                                       CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
+                                                                       CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
                                                                }
                                                        }
                                                        goto killparticle;
@@ -2787,10 +2813,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);