+static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
+void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
+{
+ vec3_t center;
+ matrix4x4_t tempmatrix;
+ VectorLerp(originmins, 0.5, originmaxs, center);
+ Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
+ if (effectnameindex == EFFECT_SVC_PARTICLE)
+ {
+ if (cl_particles.integer)
+ {
+ // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
+ if (count == 1024)
+ CL_ParticleExplosion(center);
+ else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
+ CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ else
+ {
+ count *= cl_particles_quality.value;
+ for (;count > 0;count--)
+ {
+ int k = particlepalette[palettecolor + (rand()&7)];
+ if (cl_particles_quake.integer)
+ particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
+ else if (gamemode == GAME_GOODVSBAD2)
+ particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
+ else
+ particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
+ }
+ }
+ }
+ }
+ else if (effectnameindex == EFFECT_TE_WIZSPIKE)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
+ else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
+ else if (effectnameindex == EFFECT_TE_SPIKE)
+ {
+ if (cl_particles_bulletimpacts.integer)
+ {
+ if (cl_particles_quake.integer)
+ {
+ if (cl_particles_smoke.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ }
+ else
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
+ }
+ // bullet hole
+ if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+ CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ }
+ else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
+ {
+ if (cl_particles_bulletimpacts.integer)
+ {
+ if (cl_particles_quake.integer)
+ {
+ if (cl_particles_smoke.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ }
+ else
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
+ }
+ // bullet hole
+ if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+ CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
+ {
+ if (cl_particles_bulletimpacts.integer)
+ {
+ if (cl_particles_quake.integer)
+ {
+ if (cl_particles_smoke.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ }
+ else
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
+ }
+ // bullet hole
+ if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+ CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ }
+ else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
+ {
+ if (cl_particles_bulletimpacts.integer)
+ {
+ if (cl_particles_quake.integer)
+ {
+ if (cl_particles_smoke.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ }
+ else
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
+ }
+ // bullet hole
+ if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+ CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_BLOOD)
+ {
+ if (!cl_particles_blood.integer)
+ return;
+ if (cl_particles_quake.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
+ else
+ {
+ static double bloodaccumulator = 0;
+ bloodaccumulator += count * 0.333 * cl_particles_quality.value;
+ for (;bloodaccumulator > 0;bloodaccumulator--)
+ particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
+ }
+ }
+ else if (effectnameindex == EFFECT_TE_SPARK)
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
+ else if (effectnameindex == EFFECT_TE_PLASMABURN)
+ {
+ // plasma scorch mark
+ if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
+ CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_GUNSHOT)
+ {
+ if (cl_particles_bulletimpacts.integer)
+ {
+ if (cl_particles_quake.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ else
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
+ }
+ // bullet hole
+ if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+ CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ }
+ else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
+ {
+ if (cl_particles_bulletimpacts.integer)
+ {
+ if (cl_particles_quake.integer)
+ CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
+ else
+ CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
+ }
+ // bullet hole
+ if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
+ CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_EXPLOSION)
+ {
+ CL_ParticleExplosion(center);
+ CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
+ {
+ CL_ParticleExplosion(center);
+ CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
+ {
+ if (cl_particles_quake.integer)
+ {
+ int i;
+ for (i = 0;i < 1024 * cl_particles_quality.value;i++)
+ {
+ if (i & 1)
+ particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
+ else
+ particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
+ }
+ }
+ else
+ CL_ParticleExplosion(center);
+ CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_SMALLFLASH)
+ CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ else if (effectnameindex == EFFECT_TE_FLAMEJET)
+ {
+ count *= cl_particles_quality.value;
+ while (count-- > 0)
+ particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
+ }
+ else if (effectnameindex == EFFECT_TE_LAVASPLASH)
+ {
+ float i, j, inc, vel;
+ vec3_t dir, org;
+
+ inc = 8 / cl_particles_quality.value;
+ for (i = -128;i < 128;i += inc)
+ {
+ for (j = -128;j < 128;j += inc)
+ {
+ dir[0] = j + lhrandom(0, inc);
+ dir[1] = i + lhrandom(0, inc);
+ dir[2] = 256;
+ org[0] = center[0] + dir[0];
+ org[1] = center[1] + dir[1];
+ org[2] = center[2] + lhrandom(0, 64);
+ vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
+ particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
+ }
+ }
+ }
+ else if (effectnameindex == EFFECT_TE_TELEPORT)
+ {
+ float i, j, k, inc, vel;
+ vec3_t dir;
+
+ inc = 8 / cl_particles_quality.value;
+ for (i = -16;i < 16;i += inc)
+ {
+ for (j = -16;j < 16;j += inc)
+ {
+ for (k = -24;k < 32;k += inc)
+ {
+ VectorSet(dir, i*8, j*8, k*8);
+ VectorNormalize(dir);
+ vel = lhrandom(50, 113);
+ particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
+ }
+ }
+ }
+ particle(particletype + pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
+ CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_TEI_G3)
+ particle(particletype + pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
+ else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
+ {
+ if (cl_particles_smoke.integer)
+ {
+ count *= 0.25f * cl_particles_quality.value;
+ while (count-- > 0)
+ particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
+ }
+ }
+ else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
+ {
+ CL_ParticleExplosion(center);
+ CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
+ {
+ float f;
+ if (cl_stainmaps.integer)
+ R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
+ CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
+ if (cl_particles_smoke.integer)
+ for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
+ particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
+ if (cl_particles_sparks.integer)
+ for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
+ particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
+ CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_EF_FLAME)
+ {
+ count *= 300 * cl_particles_quality.value;
+ while (count-- > 0)
+ particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
+ CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (effectnameindex == EFFECT_EF_STARDUST)
+ {
+ count *= 200 * cl_particles_quality.value;
+ while (count-- > 0)
+ particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
+ CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
+ {
+ vec3_t dir, pos;
+ float len, dec, qd;
+ int smoke, blood, bubbles, r, color;
+
+ if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
+ {
+ vec4_t light;
+ Vector4Set(light, 0, 0, 0, 0);
+
+ if (effectnameindex == EFFECT_TR_ROCKET)
+ Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
+ else if (effectnameindex == EFFECT_TR_VORESPIKE)
+ {
+ if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
+ Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
+ else
+ Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
+ }
+ else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
+ Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
+
+ if (light[3])
+ {
+ matrix4x4_t tempmatrix;
+ Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
+ R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ }
+
+ if (!spawnparticles)
+ return;
+
+ if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
+ return;
+
+ VectorSubtract(originmaxs, originmins, dir);
+ len = VectorNormalizeLength(dir);
+ if (ent)
+ {
+ dec = -ent->persistent.trail_time;
+ ent->persistent.trail_time += len;
+ if (ent->persistent.trail_time < 0.01f)
+ return;
+
+ // if we skip out, leave it reset
+ ent->persistent.trail_time = 0.0f;
+ }
+ else
+ dec = 0;
+
+ // advance into this frame to reach the first puff location
+ VectorMA(originmins, dec, dir, pos);
+ len -= dec;
+
+ smoke = cl_particles.integer && cl_particles_smoke.integer;
+ blood = cl_particles.integer && cl_particles_blood.integer;
+ bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
+ qd = 1.0f / cl_particles_quality.value;
+
+ while (len >= 0)
+ {
+ dec = 3;
+ if (blood)
+ {
+ if (effectnameindex == EFFECT_TR_BLOOD)
+ {
+ if (cl_particles_quake.integer)
+ {
+ color = particlepalette[67 + (rand()&3)];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+ }
+ else
+ {
+ dec = 16;
+ particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
+ }
+ }
+ else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
+ {
+ if (cl_particles_quake.integer)
+ {
+ dec = 6;
+ color = particlepalette[67 + (rand()&3)];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+ }
+ else
+ {
+ dec = 32;
+ particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
+ }
+ }
+ }
+ if (smoke)
+ {
+ if (effectnameindex == EFFECT_TR_ROCKET)
+ {
+ if (cl_particles_quake.integer)
+ {
+ r = rand()&3;
+ color = particlepalette[ramp3[r]];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+ }
+ else
+ {
+ particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20);
+ }
+ }
+ else if (effectnameindex == EFFECT_TR_GRENADE)
+ {
+ if (cl_particles_quake.integer)
+ {
+ r = 2 + (rand()%5);
+ color = particlepalette[ramp3[r]];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
+ }
+ else
+ {
+ particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ }
+ else if (effectnameindex == EFFECT_TR_WIZSPIKE)
+ {
+ if (cl_particles_quake.integer)
+ {
+ dec = 6;
+ color = particlepalette[52 + (rand()&7)];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
+ }
+ else if (gamemode == GAME_GOODVSBAD2)
+ {
+ dec = 6;
+ particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ else
+ {
+ color = particlepalette[20 + (rand()&7)];
+ particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ }
+ else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
+ {
+ if (cl_particles_quake.integer)
+ {
+ dec = 6;
+ color = particlepalette[230 + (rand()&7)];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
+ }
+ else
+ {
+ color = particlepalette[226 + (rand()&7)];
+ particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ }
+ else if (effectnameindex == EFFECT_TR_VORESPIKE)
+ {
+ if (cl_particles_quake.integer)
+ {
+ color = particlepalette[152 + (rand()&3)];
+ particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
+ }
+ else if (gamemode == GAME_GOODVSBAD2)
+ {
+ dec = 6;
+ particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ else if (gamemode == GAME_PRYDON)
+ {
+ dec = 6;
+ particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ else
+ particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
+ {
+ dec = 7;
+ particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
+ }
+ else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
+ {
+ dec = 4;
+ particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
+ }
+ else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
+ particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
+ }
+ if (bubbles)
+ {
+ if (effectnameindex == EFFECT_TR_ROCKET)
+ particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
+ else if (effectnameindex == EFFECT_TR_GRENADE)
+ particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
+ }
+ // advance to next time and position
+ dec *= qd;
+ len -= dec;
+ VectorMA (pos, dec, dir, pos);
+ }
+ 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]);
+}
+
+// this is also called on point effects with spawndlight = true and
+// spawnparticles = true
+// it is called CL_ParticleTrail because most code does not want to supply
+// these parameters, only trail handling does
+void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
+{
+ vec3_t center;
+ qboolean found = false;
+ if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
+ return; // invalid effect index
+ if (!particleeffectname[effectnameindex][0])
+ return; // no such effect
+ VectorLerp(originmins, 0.5, originmaxs, center);
+ if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
+ {
+ int effectinfoindex;
+ int supercontents;
+ int tex;
+ particleeffectinfo_t *info;
+ vec3_t center;
+ vec3_t centervelocity;
+ vec3_t traildir;
+ vec3_t trailpos;
+ vec3_t rvec;
+ vec_t traillen;
+ vec_t trailstep;
+ qboolean underwater;
+ // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
+ VectorLerp(originmins, 0.5, originmaxs, center);
+ VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
+ supercontents = CL_PointSuperContents(center);
+ underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
+ VectorSubtract(originmaxs, originmins, traildir);
+ traillen = VectorLength(traildir);
+ VectorNormalize(traildir);
+ for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
+ {
+ if (info->effectnameindex == effectnameindex)
+ {
+ found = true;
+ if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
+ continue;
+ if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
+ continue;
+
+ // spawn a dlight if requested
+ if (info->lightradiusstart > 0 && spawndlight)
+ {
+ matrix4x4_t tempmatrix;
+ if (info->trailspacing > 0)
+ Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
+ else
+ Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
+ if (info->lighttime > 0 && info->lightradiusfade > 0)
+ {
+ // light flash (explosion, etc)
+ // called when effect starts
+ CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ else
+ {
+ // glowing entity
+ // called by CL_LinkNetworkEntity
+ Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
+ R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
+ }
+ }
+
+ if (!spawnparticles)
+ continue;
+
+ // spawn particles
+ tex = info->tex[0];
+ if (info->tex[1] > info->tex[0])
+ {
+ tex = (int)lhrandom(info->tex[0], info->tex[1]);
+ tex = min(tex, info->tex[1] - 1);
+ }
+ if (info->particletype == pt_decal)
+ CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
+ else if (info->particletype == pt_beam)
+ particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
+ else
+ {
+ if (!cl_particles.integer)
+ continue;
+ switch (info->particletype)
+ {
+ case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
+ case pt_spark: if (!cl_particles_sparks.integer) continue;break;
+ case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
+ case pt_blood: if (!cl_particles_blood.integer) continue;break;
+ default: break;
+ }
+ VectorCopy(originmins, trailpos);
+ if (info->trailspacing > 0)
+ {
+ info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
+ trailstep = info->trailspacing / cl_particles_quality.value;
+ }
+ else
+ {
+ info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
+ trailstep = 0;
+ }
+ info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
+ for (;info->particleaccumulator >= 1;info->particleaccumulator--)
+ {
+ if (info->tex[1] > info->tex[0])
+ {
+ tex = (int)lhrandom(info->tex[0], info->tex[1]);
+ tex = min(tex, info->tex[1] - 1);
+ }
+ if (!trailstep)
+ {
+ trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
+ trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
+ trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
+ }
+ VectorRandom(rvec);
+ particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0);
+ if (trailstep)
+ VectorMA(trailpos, trailstep, traildir, trailpos);
+ }
+ }
+ }
+ }
+ }
+ if (!found)
+ CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
+}
+
+void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor)
+{
+ CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
+}
+