The die field in struct particle_t stores the time when the particle should disappear... but not always. Usually, it is set as in p->die = cl.time + 5, but on rocket trails, grenade trails and explosions, p->die is modified by p->type and p->ramp.
First, let's take a look at the ramp tables:
Code: Select all
int
ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61} // pt_explode
, ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66} // pt_explode2
, ramp3[6] = {0x6d, 0x6b, 6, 5, 4, 3, } // pt_fire (rocket trail and smoke trail only) (smoke offset by 2)
;
The code above is already fixed by me. Originally, ramp3 had a size [8], with only the first 6 numbers defined; the last two undefined values weren't supposed to be used... but the vanilla code could actually access it randomly due to poor bounds checking.
See this code:
Code: Select all
void R_RocketTrail (vec3_t start, vec3_t end, int type)
{
[...]
while (len > 0)
{
[...]
p->die = cl.time + 2;
p->start_time = cl.time; // mankrip
switch (type) // trail type, not particle type
{
case 0: // rocket trail
p->ramp = rand()&3; // 8 colors, from zero to 7
// mankrip -- fix ramp range - begin
if (p->ramp >= 6) // there's only 6 colors in this ramp (clamped by R_DrawParticles)
p->ramp -= 6;
// mankrip -- fix ramp range - end
p->die = cl.time + (6 - p->ramp) / 5.0f; // for frametime * 5.0f // mankrip - p->die timing fix
p->color = ramp3[ (int)p->ramp];//109 107 6 5
p->type = pt_fire;
for (j=0 ; j<3 ; j++)
p->org[j] = start[j] + ((rand()%6)-3);
break;
case 1: // smoke trail
p->ramp = 2 + (rand () & 2); // mankrip -- fix ramp range -- skip fire colors, get only grayscale -- there's only 4 grayscale colors in ramp
p->die = cl.time + (6 - p->ramp) / 5.0f; // for frametime * 5.0f // mankrip - p->die timing fix
p->color = ramp3[ (int)p->ramp];
p->type = pt_fire;
for (j=0 ; j<3 ; j++)
p->org[j] = start[j] + ((rand()%6)-3);
break;
The equivalent fix for grenade trails was simpler, since it always skips the first two indexes of ramp3 and only uses the four indexes of gray tones. Here, the vanilla code was even worse, allowing values all the way up to 9 (zero to 7, plus the offset 2). All I had to do was adjust the bitmask to 2, to get the correct range of zero to 4 (plus the offset 2).
And the last fix in the R_RocketTrail function above was to recalculate p->die according to the resulting p->ramp values. It is defined as cl.time plus the remaining countdown of p->ramp divided by the default framerate of each trail. These default framerate values are defined by the vanilla code in R_DrawParticles:
Code: Select all
time3 = frametime * 15.0f;
time2 = frametime * 10.0f; // 15;
time1 = frametime * 5.0f;
Code: Select all
void R_ParticleExplosion (vec3_t org)
{
int i, j;
particle_t *p;
for (i=0 ; i<1024 ; i++)
{
if (!free_particles)
return;
p = free_particles;
free_particles = p->next;
p->next = active_particles;
active_particles = p;
p->start_time = cl.time; // mankrip
p->color = ramp1[0];
p->ramp = rand()&3; // 8 colors, from zero to 7
// calculate the amount of ramp steps, and multiply by the time of each ramp
if (i & 1)
{
p->type = pt_explode;
p->die = cl.time + (8 - p->ramp) / 10.0f; // for frametime * 10.0f // mankrip - p->die timing fix
}
else
{
p->type = pt_explode2;
p->die = cl.time + (8 - p->ramp) / 15.0f; // for frametime * 15.0f // mankrip - p->die timing fix
}
for (j=0 ; j<3 ; j++)
{
p->org[j] = org[j] + ((rand()%32)-16);
p->vel[j] = (rand()%512)-256;
}
}
}
void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
{
int i, j;
particle_t *p;
for (i=0 ; i<count ; i++)
{
if (!free_particles)
return;
p = free_particles;
free_particles = p->next;
p->next = active_particles;
active_particles = p;
if (count == 1024)
{ // rocket explosion
p->start_time = cl.time;
p->color = ramp1[0];
p->ramp = rand()&3;
// calculate the amount of ramp steps, and multiply by the time of each ramp
if (i & 1)
{
p->type = pt_explode;
p->die = cl.time + (8 - p->ramp) / 10.0f; // for frametime * 10.0f // mankrip - p->die timing fix
}
else
{
p->type = pt_explode2;
p->die = cl.time + (8 - p->ramp) / 15.0f; // for frametime * 15.0f // mankrip - p->die timing fix
}
for (j=0 ; j<3 ; j++)
{
p->org[j] = org[j] + ((rand()%32)-16);
p->vel[j] = (rand()%512)-256;
}
}
else
{
p->start_time = cl.time; // mankrip
p->die = cl.time + 0.1*(rand()%5);
p->color = (color&~7) + (rand()&7);
p->type = pt_slowgrav;
for (j=0 ; j<3 ; j++)
{
p->org[j] = org[j] + ((rand()&15)-8);
p->vel[j] = dir[j]*15;// + (rand()%300)-150;
}
}
}
}
Code: Select all
void R_DrawParticles (void)
{
[...]
time3 = frametime * 15.0f;
time2 = frametime * 10.0f; // 15;
time1 = frametime * 5.0f;
[...]
for (p=active_particles ; p ; p=p->next)
{
[...]
switch (p->type)
{
case pt_static:
break;
case pt_fire:
p->ramp += time1;
if (p->ramp >= 6) // set particle to be removed on the next frame
p->die = cl.time - 0.01f; // mankrip - was -1, screwing up particle alpha
else
p->color = ramp3[(int)p->ramp];
p->vel[2] += grav;
break;
case pt_explode:
p->ramp += time2;
if (p->ramp >= 8) // set particle to be removed on the next frame
p->die = cl.time - 0.01f; // mankrip - was -1, screwing up particle alpha
else
p->color = ramp1[(int)p->ramp];
// mankrip - unroll - begin
p->vel[0] += p->vel[0]*dvel;
p->vel[1] += p->vel[1]*dvel;
p->vel[2] += p->vel[2]*dvel - grav;
// mankrip - unroll - end
break;
case pt_explode2:
p->ramp += time3;
if (p->ramp >= 8) // set particle to be removed on the next frame
p->die = cl.time - 0.01f; // mankrip - was -1, screwing up particle alpha
else
p->color = ramp2[(int)p->ramp];
// mankrip - unroll - begin
p->vel[0] -= p->vel[0]*frametime;
p->vel[1] -= p->vel[1]*frametime;
p->vel[2] = p->vel[2] - p->vel[2]*frametime - grav;
// mankrip - unroll - end
break;
Now, with all these fixes in place, we can finally use the particle timing for other effects, such as translucency, with accurate results on all kinds of particles:
Code: Select all
if (r_particle_blend_mode.value)
pparticle->alpha = 1.0f - ( (float)cl.time - pparticle->start_time) / (pparticle->die - pparticle->start_time);
else
pparticle->alpha = 1.0f;