-static int cl_maxparticles;
-static int cl_numparticles;
-static int cl_freeparticle;
-static particle_t *particles;
-
-cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1"};
-cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1"};
-cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1"};
-cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0"};
-cvar_t cl_particles_bloodshowers = {CVAR_SAVE, "cl_particles_bloodshowers", "1"};
-cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1"};
-cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5"};
-cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1"};
-cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1"};
-cvar_t cl_particles_explosions_bubbles = {CVAR_SAVE, "cl_particles_explosions_bubbles", "1"};
-cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0"};
-cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1"};
-cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0"};
-cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1"};
-cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5"};
-cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55"};
-cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1"};
-cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1"};
-cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0"};
-cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0"};
-cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20"};
-
-void CL_Particles_Clear(void)
+cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
+cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
+cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
+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_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"};
+cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
+cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
+cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
+cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
+cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
+cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
+cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
+cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
+cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
+cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
+cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
+cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
+cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
+
+
+void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
+{
+ int arrayindex;
+ int argc;
+ int effectinfoindex;
+ int linenumber;
+ particleeffectinfo_t *info = NULL;
+ const char *text = textstart;
+ char argv[16][1024];
+ effectinfoindex = -1;
+ for (linenumber = 1;;linenumber++)
+ {
+ argc = 0;
+ for (arrayindex = 0;arrayindex < 16;arrayindex++)
+ argv[arrayindex][0] = 0;
+ for (;;)
+ {
+ if (!COM_ParseToken_Simple(&text, true, false))
+ return;
+ if (!strcmp(com_token, "\n"))
+ break;
+ if (argc < 16)
+ {
+ strlcpy(argv[argc], com_token, sizeof(argv[argc]));
+ argc++;
+ }
+ }
+ 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 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)
+#define readfloat(var) checkparms(2);var = atof(argv[1])
+ if (!strcmp(argv[0], "effect"))
+ {
+ int effectnameindex;
+ checkparms(2);
+ effectinfoindex++;
+ if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
+ {
+ Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
+ break;
+ }
+ for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
+ {
+ if (particleeffectname[effectnameindex][0])
+ {
+ if (!strcmp(particleeffectname[effectnameindex], argv[1]))
+ break;
+ }
+ else
+ {
+ strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
+ break;
+ }
+ }
+ // if we run out of names, abort
+ if (effectnameindex == MAX_PARTICLEEFFECTNAME)
+ {
+ Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
+ break;
+ }
+ info = particleeffectinfo + effectinfoindex;
+ info->effectnameindex = effectnameindex;
+ info->particletype = pt_alphastatic;
+ info->blendmode = particletype[info->particletype].blendmode;
+ info->orientation = particletype[info->particletype].orientation;
+ info->tex[0] = tex_particle;
+ info->tex[1] = tex_particle;
+ info->color[0] = 0xFFFFFF;
+ info->color[1] = 0xFFFFFF;
+ info->size[0] = 1;
+ info->size[1] = 1;
+ info->alpha[0] = 0;
+ info->alpha[1] = 256;
+ info->alpha[2] = 256;
+ info->time[0] = 9999;
+ info->time[1] = 9999;
+ VectorSet(info->lightcolor, 1, 1, 1);
+ info->lightshadow = true;
+ info->lighttime = 9999;
+ info->stretchfactor = 1;
+ info->staincolor[0] = -1;
+ info->staincolor[1] = -1;
+ info->staintex[0] = -1;
+ info->staintex[1] = -1;
+ }
+ else if (info == NULL)
+ {
+ Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
+ break;
+ }
+ else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
+ else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
+ else if (!strcmp(argv[0], "type"))
+ {
+ checkparms(2);
+ if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
+ else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
+ else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
+ else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
+ else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
+ else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
+ else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
+ else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
+ else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
+ 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]);
+ info->blendmode = particletype[info->particletype].blendmode;
+ info->orientation = particletype[info->particletype].orientation;
+ }
+ else if (!strcmp(argv[0], "blend"))
+ {
+ checkparms(2);
+ 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 if (!strcmp(argv[0], "orientation"))
+ {
+ checkparms(2);
+ if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
+ 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 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")) {readfloats(info->time, 2);}
+ else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
+ else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
+ else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
+ else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
+ else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
+ else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
+ else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
+ else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
+ else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
+ else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
+ else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
+ else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
+ else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
+ else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
+ else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
+ else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
+ else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
+ else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
+ else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
+ else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
+ else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
+ else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = -1; info->staincolor[1] = -1;}
+ else
+ Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
+#undef checkparms
+#undef readints
+#undef readfloats
+#undef readint
+#undef readfloat
+ }
+}
+
+int CL_ParticleEffectIndexForName(const char *name)
+{
+ int i;
+ for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
+ if (!strcmp(particleeffectname[i], name))
+ return i;
+ return 0;
+}
+
+const char *CL_ParticleEffectNameForIndex(int i)