+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"};
+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, const char *filename)
+{
+ 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("%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)
+#define readfloat(var) checkparms(2);var = atof(argv[1])
+#define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
+ if (!strcmp(argv[0], "effect"))
+ {
+ int effectnameindex;
+ checkparms(2);
+ effectinfoindex++;
+ if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
+ {
+ Con_Printf("%s:%i: too many effects!\n", filename, 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("%s:%i: too many effects!\n", filename, 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] = (unsigned int)-1;
+ info->staincolor[1] = (unsigned int)-1;
+ info->staintex[0] = -1;
+ info->staintex[1] = -1;
+ }
+ else if (info == NULL)
+ {
+ Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
+ break;
+ }
+ else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
+ 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("%s:%i: unrecognized particle type %s\n", filename, 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("%s:%i: unrecognized blendmode %s\n", filename, 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("%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);}
+ 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")) {readbool(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] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
+ else
+ Con_Printf("%s:%i: skipping unknown command %s\n", filename, 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)
+{
+ if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
+ return NULL;
+ return particleeffectname[i];
+}
+
+// MUST match effectnameindex_t in client.h
+static const char *standardeffectnames[EFFECT_TOTAL] =
+{
+ "",
+ "TE_GUNSHOT",
+ "TE_GUNSHOTQUAD",
+ "TE_SPIKE",
+ "TE_SPIKEQUAD",
+ "TE_SUPERSPIKE",
+ "TE_SUPERSPIKEQUAD",
+ "TE_WIZSPIKE",
+ "TE_KNIGHTSPIKE",
+ "TE_EXPLOSION",
+ "TE_EXPLOSIONQUAD",
+ "TE_TAREXPLOSION",
+ "TE_TELEPORT",
+ "TE_LAVASPLASH",
+ "TE_SMALLFLASH",
+ "TE_FLAMEJET",
+ "EF_FLAME",
+ "TE_BLOOD",
+ "TE_SPARK",
+ "TE_PLASMABURN",
+ "TE_TEI_G3",
+ "TE_TEI_SMOKE",
+ "TE_TEI_BIGEXPLOSION",
+ "TE_TEI_PLASMAHIT",
+ "EF_STARDUST",
+ "TR_ROCKET",
+ "TR_GRENADE",
+ "TR_BLOOD",
+ "TR_WIZSPIKE",
+ "TR_SLIGHTBLOOD",
+ "TR_KNIGHTSPIKE",
+ "TR_VORESPIKE",
+ "TR_NEHAHRASMOKE",
+ "TR_NEXUIZPLASMA",
+ "TR_GLOWTRAIL",
+ "SVC_PARTICLE"
+};
+
+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]));
+ for (filepass = 0;;filepass++)
+ {
+ 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);
+ }
+}