merged CL_MoveParticles, CL_MoveDecals, and R_MoveExplosions into their
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Tue, 22 Jan 2008 20:05:42 +0000 (20:05 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Tue, 22 Jan 2008 20:05:42 +0000 (20:05 +0000)
respective Draw functions, this gave a small fps gain (due to better
cache behavior)
redesigned input packet timing (now based on cl.time instead of
realtime, which required compensating for slowmo), this takes advantage
of the time synchronization features of the cl.time code
replaced cls.movesequence with cls.netcon->outgoing_unreliable_sequence

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@7996 d7cf8633-e32d-0410-b094-e92efae38249

cl_input.c
cl_main.c
cl_particles.c
client.h
netconn.c
netconn.h
r_explosion.c

index 0614493..1d5127d 100644 (file)
@@ -1154,10 +1154,10 @@ extern cvar_t slowmo;
 void CL_UpdateMoveVars(void)
 {
        if (cls.protocol == PROTOCOL_QUAKEWORLD)
-               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond_qw.value, 100);
+               cl.movevars_packetinterval = 1.0 / bound(1, cl_netinputpacketspersecond_qw.value, 100);
        else if (cl.stats[STAT_MOVEVARS_TICRATE])
        {
-               cl.movevars_ticrate = cl.statsf[STAT_MOVEVARS_TICRATE];
+               cl.movevars_packetinterval = cl.statsf[STAT_MOVEVARS_TICRATE] * cl.statsf[STAT_MOVEVARS_TIMESCALE] / bound(1, cl_netinputpacketsperserverpacket.value, 10);
                cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE];
                cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY];
                cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ;
@@ -1179,7 +1179,7 @@ void CL_UpdateMoveVars(void)
        }
        else
        {
-               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond.value, 100);
+               cl.movevars_packetinterval = slowmo.value / bound(1, cl_netinputpacketspersecond.value, 100);
                cl.movevars_timescale = slowmo.value;
                cl.movevars_gravity = sv_gravity.value;
                cl.movevars_stopspeed = cl_movement_stopspeed.value;
@@ -1357,7 +1357,6 @@ void CL_SendMove(void)
        int bits;
        sizebuf_t buf;
        unsigned char data[1024];
-       static double lastsendtime = 0;
        double packettime;
        int msecdelta;
 
@@ -1368,54 +1367,22 @@ void CL_SendMove(void)
                return;
 
        // don't send too often or else network connections can get clogged by a high renderer framerate
-       packettime = cl.movevars_ticrate;
-       if (cls.protocol != PROTOCOL_QUAKEWORLD)
-               packettime /= (double)bound(1, cl_netinputpacketsperserverpacket.value, 10);
+       packettime = cl.movevars_packetinterval;
        // send input every frame in singleplayer
        if (cl.islocalgame)
                packettime = 0;
-       // quakeworld servers take only frametimes
-       // predicted dp7 servers take current interpolation time
-       // unpredicted servers take an echo of the latest server timestamp
+       // send the current interpolation time
        cl.cmd.time = cl.time;
-       cl.cmd.sequence = cls.movesequence;
-       if (cls.protocol == PROTOCOL_QUAKEWORLD)
-       {
-               if (realtime < lastsendtime + packettime)
-                       return;
-               cl.cmd.sequence = cls.netcon->qw.outgoing_sequence;
-       }
+       cl.cmd.sequence = cls.netcon->outgoing_unreliable_sequence;
+       if (cl.cmd.time < cl.lastpackettime + packettime && (cl.mtime[0] != cl.mtime[1] || !cl.movement_needupdate))
+               return;
+       // try to round off the lastpackettime to a multiple of the packet interval
+       // (this causes it to emit packets at a steady beat, and takes advantage
+       //  of the time drift compensation in the cl.time code)
+       if (packettime > 0)
+               cl.lastpackettime = floor(cl.cmd.time / packettime) * packettime;
        else
-       {
-               // movement should be sent immediately whenever a server
-               // packet is received, to minimize ping times
-               //
-
-               if(cl_netinputpacketsperserverpacket.value > 1)
-               {
-                       // FIXME: needupdate causes odd behaviour with movement reply as it
-                       // seems, if cl_netinputpacketsperserverpackets is > 1. Don't know
-                       // why. No idea if it ever gets fixed, but until it does...
-                       if (realtime < lastsendtime + packettime)
-                               return;
-               }
-               else if(cl_netinputpacketsperserverpacket.value == 1)
-               {
-                       // the way it is meant to be
-                       if (!cl.movement_needupdate && realtime < lastsendtime + packettime)
-                               return;
-               }
-               else
-               {
-                       // only ever send input as replies to server packets or if we REALLY got nothing else (FIXME may be the more sane default)
-                       if (!cl.movement_needupdate && realtime < lastsendtime + packettime + 0.1)
-                               return;
-               }
-       }
-
-       // don't let it fall behind if CL_SendMove hasn't been called recently
-       // (such is the case when framerate is too low for instance)
-       lastsendtime = bound(realtime, lastsendtime + packettime, realtime + packettime);
+               cl.lastpackettime = cl.cmd.time;
        // set the flag indicating that we sent a packet recently
        cl.movement_needupdate = false;
 
@@ -1432,9 +1399,6 @@ void CL_SendMove(void)
        if ((cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) && !NetConn_CanSend(cls.netcon) && !cl.islocalgame)
                return;
 
-       // increase the move counter since we intend to send a move
-       cls.movesequence++;
-
        // send the movement message
        // PROTOCOL_QUAKE        clc_move = 16 bytes total
        // PROTOCOL_QUAKEDP      clc_move = 16 bytes total
@@ -1528,19 +1492,19 @@ void CL_SendMove(void)
                QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]);
                QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.movecmd[0]);
                // calculate the checksum
-               buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->qw.outgoing_sequence);
+               buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence);
                // if delta compression history overflows, request no delta
-               if (cls.netcon->qw.outgoing_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
+               if (cls.netcon->outgoing_unreliable_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
                        cl.qw_validsequence = 0;
                // request delta compression if appropriate
                if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
                {
-                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
+                       cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
                        MSG_WriteByte(&buf, qw_clc_delta);
                        MSG_WriteByte(&buf, cl.qw_validsequence & 255);
                }
                else
-                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = -1;
+                       cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = -1;
        }
        else if (cls.signon == SIGNONS)
        {
index d0a1010..c5f31ff 100644 (file)
--- a/cl_main.c
+++ b/cl_main.c
@@ -1801,10 +1801,7 @@ void CL_UpdateWorld(void)
                CL_RelinkLightFlashes();
                CSQC_RelinkAllEntities(ENTMASK_ENGINE | ENTMASK_ENGINEVIEWMODELS);
 
-               // move decals, particles, and any other effects
-               CL_MoveDecals();
-               CL_MoveParticles();
-               R_MoveExplosions();
+               // decals, particles, and explosions will be updated during rneder
        }
 
        r_refdef.scene.time = cl.time;
index e9def1a..55f99a5 100644 (file)
@@ -1256,6 +1256,7 @@ void CL_EntityParticles (const entity_t *ent)
        float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
        static vec3_t avelocities[NUMVERTEXNORMALS];
        if (!cl_particles.integer) return;
+       if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
 
        Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
 
@@ -1533,295 +1534,6 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
        }
 }
 
-/*
-===============
-CL_MoveDecals
-===============
-*/
-void CL_MoveDecals (void)
-{
-       decal_t *decal;
-       int i;
-       float decalfade;
-
-       // LordHavoc: early out condition
-       if (!cl.num_decals)
-       {
-               cl.free_decal = 0;
-               return;
-       }
-
-       decalfade = bound(0, cl.time - cl.oldtime, 0.1) * 255 / cl_decals_fadetime.value;
-
-       for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
-       {
-               if (!decal->typeindex)
-                       continue;
-
-               // heavily optimized decal case
-               // FIXME: this has fairly wacky handling of alpha
-               if (cl.time > decal->time2 + cl_decals_time.value)
-               {
-                       decal->alpha -= decalfade;
-                       if (decal->alpha <= 0)
-                       {
-                               decal->typeindex = 0;
-                               if (cl.free_decal > i)
-                                       cl.free_decal = i;
-                               continue;
-                       }
-               }
-
-               if (decal->owner)
-               {
-                       if (cl.entities[decal->owner].render.model == decal->ownermodel)
-                       {
-                               Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
-                               Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
-                       }
-                       else
-                       {
-                               decal->typeindex = 0;
-                               if (cl.free_decal > i)
-                                       cl.free_decal = i;
-                       }
-               }
-       }
-
-       // reduce cl.num_decals if possible
-       while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
-               cl.num_decals--;
-}
-
-/*
-===============
-CL_MoveParticles
-===============
-*/
-void CL_MoveParticles (void)
-{
-       particle_t *p;
-       int i, j, a, content;
-       float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
-       int hitent;
-       trace_t trace;
-
-       // LordHavoc: early out condition
-       if (!cl.num_particles)
-       {
-               cl.free_particle = 0;
-               return;
-       }
-
-       frametime = bound(0, cl.time - cl.oldtime, 0.1);
-       gravity = frametime * cl.movevars_gravity;
-       dvel = 1+4*frametime;
-       decalfade = frametime * 255 / cl_decals_fadetime.value;
-
-       j = 0;
-       for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
-       {
-               if (!p->typeindex)
-               {
-                       if (cl.free_particle > i)
-                               cl.free_particle = i;
-                       continue;
-               }
-
-               if (p->delayedspawn)
-               {
-                       if (p->delayedspawn > cl.time)
-                               continue;
-                       p->delayedspawn = 0;
-               }
-
-               content = 0;
-
-               p->size += p->sizeincrease * frametime;
-               p->alpha -= p->alphafade * frametime;
-
-               if (p->alpha <= 0 || p->die <= cl.time)
-               {
-                       p->typeindex = 0;
-                       if (cl.free_particle > i)
-                               cl.free_particle = i;
-                       continue;
-               }
-
-               if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
-               {
-                       if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
-                       {
-                               if (p->typeindex == pt_blood)
-                                       p->size += frametime * 8;
-                               else
-                                       p->vel[2] -= p->gravity * gravity;
-                               f = 1.0f - min(p->liquidfriction * frametime, 1);
-                               VectorScale(p->vel, f, p->vel);
-                       }
-                       else
-                       {
-                               p->vel[2] -= p->gravity * gravity;
-                               if (p->airfriction)
-                               {
-                                       f = 1.0f - min(p->airfriction * frametime, 1);
-                                       VectorScale(p->vel, f, p->vel);
-                               }
-                       }
-
-                       VectorCopy(p->org, oldorg);
-                       VectorMA(p->org, frametime, p->vel, p->org);
-                       if (p->bounce && cl.time >= p->delayedcollisions)
-                       {
-                               trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
-                               // if the trace started in or hit something of SUPERCONTENTS_NODROP
-                               // or if the trace hit something flagged as NOIMPACT
-                               // then remove the particle
-                               if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
-                               {
-                                       p->typeindex = 0;
-                                       if (cl.free_particle > i)
-                                               cl.free_particle = i;
-                                       continue;
-                               }
-                               VectorCopy(trace.endpos, p->org);
-                               // react if the particle hit something
-                               if (trace.fraction < 1)
-                               {
-                                       VectorCopy(trace.endpos, p->org);
-                                       if (p->typeindex == pt_rain)
-                                       {
-                                               // raindrop - splash on solid/water/slime/lava
-                                               int count;
-                                               // convert from a raindrop particle to a rainsplash decal
-                                               VectorCopy(trace.plane.normal, p->vel);
-                                               VectorAdd(p->org, p->vel, p->org);
-                                               p->typeindex = pt_raindecal;
-                                               p->texnum = tex_rainsplash;
-                                               p->time2 = cl.time;
-                                               p->alphafade = p->alpha / 0.4;
-                                               p->bounce = 0;
-                                               p->airfriction = 0;
-                                               p->liquidfriction = 0;
-                                               p->gravity = 0;
-                                               p->size *= 1.0f;
-                                               p->sizeincrease = p->size * 20;
-                                               count = (int)lhrandom(1, 10);
-                                               while(count--)
-                                                       CL_NewParticle(pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
-                                               continue;
-                                       }
-                                       else if (p->typeindex == pt_blood)
-                                       {
-                                               // blood - splash on solid
-                                               if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
-                                               {
-                                                       p->typeindex = 0;
-                                                       continue;
-                                               }
-                                               if (cl_stainmaps.integer)
-                                                       R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
-                                               if (!cl_decals.integer)
-                                               {
-                                                       p->typeindex = 0;
-                                                       continue;
-                                               }
-                                               // 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);
-                                               p->typeindex = 0;
-                                               if (cl.free_particle > i)
-                                                       cl.free_particle = i;
-                                               continue;
-                                       }
-                                       else if (p->bounce < 0)
-                                       {
-                                               // bounce -1 means remove on impact
-                                               p->typeindex = 0;
-                                               if (cl.free_particle > i)
-                                                       cl.free_particle = i;
-                                               continue;
-                                       }
-                                       else
-                                       {
-                                               // anything else - bounce off solid
-                                               dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
-                                               VectorMA(p->vel, dist, trace.plane.normal, p->vel);
-                                               if (DotProduct(p->vel, p->vel) < 0.03)
-                                                       VectorClear(p->vel);
-                                       }
-                               }
-                       }
-               }
-
-               if (p->typeindex != pt_static)
-               {
-                       switch (p->typeindex)
-                       {
-                       case pt_entityparticle:
-                               // particle that removes itself after one rendered frame
-                               if (p->time2)
-                               {
-                                       p->typeindex = 0;
-                                       if (cl.free_particle > i)
-                                               cl.free_particle = i;
-                               }
-                               else
-                                       p->time2 = 1;
-                               break;
-                       case pt_blood:
-                               a = CL_PointSuperContents(p->org);
-                               if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
-                               {
-                                       p->typeindex = 0;
-                                       if (cl.free_particle > i)
-                                               cl.free_particle = i;
-                               }
-                               break;
-                       case pt_bubble:
-                               a = CL_PointSuperContents(p->org);
-                               if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
-                               {
-                                       p->typeindex = 0;
-                                       if (cl.free_particle > i)
-                                               cl.free_particle = i;
-                               }
-                               break;
-                       case pt_rain:
-                               a = CL_PointSuperContents(p->org);
-                               if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
-                               {
-                                       p->typeindex = 0;
-                                       if (cl.free_particle > i)
-                                               cl.free_particle = i;
-                               }
-                               break;
-                       case pt_snow:
-                               if (cl.time > p->time2)
-                               {
-                                       // snow flutter
-                                       p->time2 = cl.time + (rand() & 3) * 0.1;
-                                       p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
-                                       p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
-                               }
-                               a = CL_PointSuperContents(p->org);
-                               if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
-                               {
-                                       p->typeindex = 0;
-                                       if (cl.free_particle > i)
-                                               cl.free_particle = i;
-                               }
-                               break;
-                       default:
-                               break;
-                       }
-               }
-       }
-
-       // reduce cl.num_particles if possible
-       while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
-               cl.num_particles--;
-}
-
 #define MAX_PARTICLETEXTURES 64
 // particletexture_t is a rectangle in the particlefonttexture
 typedef struct particletexture_s
@@ -2177,7 +1889,6 @@ static void r_part_newmap(void)
 
 #define BATCHSIZE 256
 int particle_element3i[BATCHSIZE*6];
-float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
 
 void R_Particles_Init (void)
 {
@@ -2200,11 +1911,12 @@ void R_Particles_Init (void)
 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
 {
        int surfacelistindex;
-       int batchstart, batchcount;
        const decal_t *d;
-       pblend_t blendmode;
-       rtexture_t *texture;
        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 particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
 
        r_refdef.stats.decals += numsurfaces;
        R_Mesh_Matrix(&identitymatrix);
@@ -2218,118 +1930,107 @@ void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t
        GL_DepthTest(true);
        GL_CullFace(GL_NONE);
 
-       // first generate all the vertices at once
-       for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
+       // generate all the vertices at once
+       for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
        {
-               particletexture_t *tex;
-               const float *org;
-               float right[3], up[3], fog, cr, cg, cb, ca, size;
-
                d = cl.decals + surfacelist[surfacelistindex];
 
-               //blendmode = particletype[d->typeindex].blendmode;
-
-               cr = d->color[0] * (1.0f / 255.0f) * r_refdef.view.colorscale;
-               cg = d->color[1] * (1.0f / 255.0f) * r_refdef.view.colorscale;
-               cb = d->color[2] * (1.0f / 255.0f) * r_refdef.view.colorscale;
-               ca = d->alpha * (1.0f / 255.0f);
-               //if (blendmode == PBLEND_MOD)
-               {
-                       cr *= ca;
-                       cg *= ca;
-                       cb *= ca;
-                       cr = min(cr, 1);
-                       cg = min(cg, 1);
-                       cb = min(cb, 1);
-                       ca = 1;
-               }
-               ca *= cl_particles_alpha.value;
+               // calculate color
+               c4f = particle_color4f + 16*surfacelistindex;
+               ca = d->alpha * alphascale;
                if (r_refdef.fogenabled)
-               {
-                       fog = FogPoint_World(d->org);
-                       cr = cr * fog;
-                       cg = cg * fog;
-                       cb = cb * fog;
-                       //if (blendmode == PBLEND_ALPHA)
-                       //{
-                       //      fog = 1 - fog;
-                       //      cr += r_refdef.fogcolor[0] * fog;
-                       //      cg += r_refdef.fogcolor[1] * fog;
-                       //      cb += r_refdef.fogcolor[2] * fog;
-                       //}
-               }
-               c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
-               c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
-               c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
-               c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
+                       ca *= FogPoint_World(d->org);
+               Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
+               Vector4Copy(c4f, c4f + 4);
+               Vector4Copy(c4f, c4f + 8);
+               Vector4Copy(c4f, c4f + 12);
 
+               // calculate vertex positions
                size = d->size * cl_particles_size.value;
-               org = d->org;
-               tex = &particletexture[d->texnum];
-
-               // PARTICLE_ORIENTED_DOUBLESIDED
                VectorVectors(d->normal, right, up);
                VectorScale(right, size, right);
                VectorScale(up, size, up);
-               v3f[ 0] = org[0] - right[0] - up[0];
-               v3f[ 1] = org[1] - right[1] - up[1];
-               v3f[ 2] = org[2] - right[2] - up[2];
-               v3f[ 3] = org[0] - right[0] + up[0];
-               v3f[ 4] = org[1] - right[1] + up[1];
-               v3f[ 5] = org[2] - right[2] + up[2];
-               v3f[ 6] = org[0] + right[0] + up[0];
-               v3f[ 7] = org[1] + right[1] + up[1];
-               v3f[ 8] = org[2] + right[2] + up[2];
-               v3f[ 9] = org[0] + right[0] - up[0];
-               v3f[10] = org[1] + right[1] - up[1];
-               v3f[11] = org[2] + right[2] - up[2];
+               v3f = particle_vertex3f + 12*surfacelistindex;
+               v3f[ 0] = d->org[0] - right[0] - up[0];
+               v3f[ 1] = d->org[1] - right[1] - up[1];
+               v3f[ 2] = d->org[2] - right[2] - up[2];
+               v3f[ 3] = d->org[0] - right[0] + up[0];
+               v3f[ 4] = d->org[1] - right[1] + up[1];
+               v3f[ 5] = d->org[2] - right[2] + up[2];
+               v3f[ 6] = d->org[0] + right[0] + up[0];
+               v3f[ 7] = d->org[1] + right[1] + up[1];
+               v3f[ 8] = d->org[2] + right[2] + up[2];
+               v3f[ 9] = d->org[0] + right[0] - up[0];
+               v3f[10] = d->org[1] + right[1] - up[1];
+               v3f[11] = d->org[2] + right[2] - up[2];
+
+               // calculate texcoords
+               tex = &particletexture[d->texnum];
+               t2f = particle_texcoord2f + 8*surfacelistindex;
                t2f[0] = tex->s1;t2f[1] = tex->t2;
                t2f[2] = tex->s1;t2f[3] = tex->t1;
                t2f[4] = tex->s2;t2f[5] = tex->t1;
                t2f[6] = tex->s2;t2f[7] = tex->t2;
        }
 
-       // now render batches of particles based on blendmode and texture
-       blendmode = PBLEND_ADD;
-       GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
-       texture = particletexture[63].texture;
-       R_Mesh_TexBind(0, R_GetTexture(texture));
+       // 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);
-       batchstart = 0;
-       batchcount = 0;
-       for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
+       R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
+       GL_LockArrays(0, 0);
+}
+
+void R_DrawDecals (void)
+{
+       int i;
+       decal_t *decal;
+       float frametime;
+       float decalfade;
+
+       frametime = bound(0, cl.time - cl.decals_updatetime, 1);
+       cl.decals_updatetime += frametime;
+
+       // LordHavoc: early out conditions
+       if ((!cl.num_decals) || (!r_drawdecals.integer))
+               return;
+
+       decalfade = frametime * 256 / cl_decals_fadetime.value;
+
+       for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
        {
-               d = cl.decals + surfacelist[surfacelistindex];
+               if (!decal->typeindex)
+                       continue;
 
-               if (blendmode != particletype[d->typeindex].blendmode)
+               if (cl.time > decal->time2 + cl_decals_time.value)
                {
-                       if (batchcount > 0)
-                               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
-                       batchcount = 0;
-                       batchstart = surfacelistindex;
-                       blendmode = particletype[d->typeindex].blendmode;
-                       if (blendmode == PBLEND_ALPHA)
-                               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-                       else if (blendmode == PBLEND_ADD)
-                               GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
-                       else //if (blendmode == PBLEND_MOD)
-                               GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+                       decal->alpha -= decalfade;
+                       if (decal->alpha <= 0)
+                               goto killdecal;
                }
-               if (texture != particletexture[d->texnum].texture)
+
+               if (decal->owner)
                {
-                       if (batchcount > 0)
-                               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
-                       batchcount = 0;
-                       batchstart = surfacelistindex;
-                       texture = particletexture[d->texnum].texture;
-                       R_Mesh_TexBind(0, R_GetTexture(texture));
+                       if (cl.entities[decal->owner].render.model == decal->ownermodel)
+                       {
+                               Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
+                               Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
+                       }
+                       else
+                               goto killdecal;
                }
-
-               batchcount++;
+               R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
+               continue;
+killdecal:
+               decal->typeindex = 0;
+               if (cl.free_decal > i)
+                       cl.free_decal = i;
        }
-       if (batchcount > 0)
-               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
-       GL_LockArrays(0, 0);
+
+       // reduce cl.num_decals if possible
+       while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
+               cl.num_decals--;
 }
 
 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
@@ -2340,6 +2041,13 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        pblend_t blendmode;
        rtexture_t *texture;
        float *v3f, *t2f, *c4f;
+       particletexture_t *tex;
+       float up2[3], v[3], right[3], up[3], fog, ifog, size;
+       float ambient[3], diffuse[3], diffusenormal[3];
+       vec4_t colormultiplier;
+       float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
+
+       Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
 
        r_refdef.stats.particles += numsurfaces;
        R_Mesh_Matrix(&identitymatrix);
@@ -2356,76 +2064,71 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        // first generate all the vertices at once
        for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
        {
-               particletexture_t *tex;
-               const float *org;
-               float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
-
                p = cl.particles + surfacelist[surfacelistindex];
 
                blendmode = particletype[p->typeindex].blendmode;
 
-               cr = p->color[0] * (1.0f / 255.0f) * r_refdef.view.colorscale;
-               cg = p->color[1] * (1.0f / 255.0f) * r_refdef.view.colorscale;
-               cb = p->color[2] * (1.0f / 255.0f) * r_refdef.view.colorscale;
-               ca = p->alpha * (1.0f / 255.0f);
-               if (blendmode == PBLEND_MOD)
-               {
-                       cr *= ca;
-                       cg *= ca;
-                       cb *= ca;
-                       cr = min(cr, 1);
-                       cg = min(cg, 1);
-                       cb = min(cb, 1);
-                       ca = 1;
-               }
-               ca *= cl_particles_alpha.value;
-               if (particletype[p->typeindex].lighting)
+               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)
                {
-                       float ambient[3], diffuse[3], diffusenormal[3];
-                       R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
-                       cr *= (ambient[0] + 0.5 * diffuse[0]);
-                       cg *= (ambient[1] + 0.5 * diffuse[1]);
-                       cb *= (ambient[2] + 0.5 * diffuse[2]);
-               }
-               if (r_refdef.fogenabled)
-               {
-                       fog = FogPoint_World(p->org);
-                       cr = cr * fog;
-                       cg = cg * fog;
-                       cb = cb * fog;
-                       if (blendmode == PBLEND_ALPHA)
+               case PBLEND_MOD:
+               case PBLEND_ADD:
+                       // additive and modulate can just fade out in fog (this is correct)
+                       if (r_refdef.fogenabled)
+                               c4f[3] *= FogPoint_World(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_ALPHA:
+                       // note: lighting is not cheap!
+                       if (particletype[p->typeindex].lighting)
                        {
-                               fog = 1 - fog;
-                               cr += r_refdef.fogcolor[0] * fog;
-                               cg += r_refdef.fogcolor[1] * fog;
-                               cb += r_refdef.fogcolor[2] * fog;
+                               R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
+                               c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
+                               c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
+                               c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
                        }
+                       // mix in the fog color
+                       if (r_refdef.fogenabled)
+                       {
+                               fog = FogPoint_World(p->org);
+                               ifog = 1 - fog;
+                               c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
+                               c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
+                               c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
+                       }
+                       break;
                }
-               c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
-               c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
-               c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
-               c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
+               // copy the color into the other three vertices
+               Vector4Copy(c4f, c4f + 4);
+               Vector4Copy(c4f, c4f + 8);
+               Vector4Copy(c4f, c4f + 12);
 
                size = p->size * cl_particles_size.value;
-               org = p->org;
                tex = &particletexture[p->texnum];
                switch(particletype[p->typeindex].orientation)
                {
                case PARTICLE_BILLBOARD:
                        VectorScale(r_refdef.view.left, -size, right);
                        VectorScale(r_refdef.view.up, size, up);
-                       v3f[ 0] = org[0] - right[0] - up[0];
-                       v3f[ 1] = org[1] - right[1] - up[1];
-                       v3f[ 2] = org[2] - right[2] - up[2];
-                       v3f[ 3] = org[0] - right[0] + up[0];
-                       v3f[ 4] = org[1] - right[1] + up[1];
-                       v3f[ 5] = org[2] - right[2] + up[2];
-                       v3f[ 6] = org[0] + right[0] + up[0];
-                       v3f[ 7] = org[1] + right[1] + up[1];
-                       v3f[ 8] = org[2] + right[2] + up[2];
-                       v3f[ 9] = org[0] + right[0] - up[0];
-                       v3f[10] = org[1] + right[1] - up[1];
-                       v3f[11] = org[2] + right[2] - up[2];
+                       v3f[ 0] = p->org[0] - right[0] - up[0];
+                       v3f[ 1] = p->org[1] - right[1] - up[1];
+                       v3f[ 2] = p->org[2] - right[2] - up[2];
+                       v3f[ 3] = p->org[0] - right[0] + up[0];
+                       v3f[ 4] = p->org[1] - right[1] + up[1];
+                       v3f[ 5] = p->org[2] - right[2] + up[2];
+                       v3f[ 6] = p->org[0] + right[0] + up[0];
+                       v3f[ 7] = p->org[1] + right[1] + up[1];
+                       v3f[ 8] = p->org[2] + right[2] + up[2];
+                       v3f[ 9] = p->org[0] + right[0] - up[0];
+                       v3f[10] = p->org[1] + right[1] - up[1];
+                       v3f[11] = p->org[2] + right[2] - up[2];
                        t2f[0] = tex->s1;t2f[1] = tex->t2;
                        t2f[2] = tex->s1;t2f[3] = tex->t1;
                        t2f[4] = tex->s2;t2f[5] = tex->t1;
@@ -2435,26 +2138,26 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        VectorVectors(p->vel, right, up);
                        VectorScale(right, size, right);
                        VectorScale(up, size, up);
-                       v3f[ 0] = org[0] - right[0] - up[0];
-                       v3f[ 1] = org[1] - right[1] - up[1];
-                       v3f[ 2] = org[2] - right[2] - up[2];
-                       v3f[ 3] = org[0] - right[0] + up[0];
-                       v3f[ 4] = org[1] - right[1] + up[1];
-                       v3f[ 5] = org[2] - right[2] + up[2];
-                       v3f[ 6] = org[0] + right[0] + up[0];
-                       v3f[ 7] = org[1] + right[1] + up[1];
-                       v3f[ 8] = org[2] + right[2] + up[2];
-                       v3f[ 9] = org[0] + right[0] - up[0];
-                       v3f[10] = org[1] + right[1] - up[1];
-                       v3f[11] = org[2] + right[2] - up[2];
+                       v3f[ 0] = p->org[0] - right[0] - up[0];
+                       v3f[ 1] = p->org[1] - right[1] - up[1];
+                       v3f[ 2] = p->org[2] - right[2] - up[2];
+                       v3f[ 3] = p->org[0] - right[0] + up[0];
+                       v3f[ 4] = p->org[1] - right[1] + up[1];
+                       v3f[ 5] = p->org[2] - right[2] + up[2];
+                       v3f[ 6] = p->org[0] + right[0] + up[0];
+                       v3f[ 7] = p->org[1] + right[1] + up[1];
+                       v3f[ 8] = p->org[2] + right[2] + up[2];
+                       v3f[ 9] = p->org[0] + right[0] - up[0];
+                       v3f[10] = p->org[1] + right[1] - up[1];
+                       v3f[11] = p->org[2] + right[2] - up[2];
                        t2f[0] = tex->s1;t2f[1] = tex->t2;
                        t2f[2] = tex->s1;t2f[3] = tex->t1;
                        t2f[4] = tex->s2;t2f[5] = tex->t1;
                        t2f[6] = tex->s2;t2f[7] = tex->t2;
                        break;
                case PARTICLE_SPARK:
-                       VectorMA(org, -0.02, p->vel, v);
-                       VectorMA(org, 0.02, p->vel, up2);
+                       VectorMA(p->org, -0.02, p->vel, v);
+                       VectorMA(p->org, 0.02, p->vel, up2);
                        R_CalcBeam_Vertex3f(v3f, v, up2, size);
                        t2f[0] = tex->s1;t2f[1] = tex->t2;
                        t2f[2] = tex->s1;t2f[3] = tex->t1;
@@ -2462,10 +2165,10 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
                        t2f[6] = tex->s2;t2f[7] = tex->t2;
                        break;
                case PARTICLE_BEAM:
-                       R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
-                       VectorSubtract(p->vel, org, up);
+                       R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
+                       VectorSubtract(p->vel, p->org, up);
                        VectorNormalize(up);
-                       v[0] = DotProduct(org, up) * (1.0f / 64.0f);
+                       v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
                        v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
                        t2f[0] = 1;t2f[1] = v[0];
                        t2f[2] = 0;t2f[3] = v[0];
@@ -2476,82 +2179,244 @@ void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtligh
        }
 
        // now render batches of particles based on blendmode and texture
-       blendmode = PBLEND_ADD;
-       GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
-       texture = particletexture[63].texture;
-       R_Mesh_TexBind(0, R_GetTexture(texture));
+       blendmode = -1;
+       texture = NULL;
        GL_LockArrays(0, numsurfaces*4);
        batchstart = 0;
        batchcount = 0;
-       for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
+       for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
        {
                p = cl.particles + surfacelist[surfacelistindex];
 
                if (blendmode != particletype[p->typeindex].blendmode)
                {
-                       if (batchcount > 0)
-                               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
-                       batchcount = 0;
-                       batchstart = surfacelistindex;
                        blendmode = particletype[p->typeindex].blendmode;
-                       if (blendmode == PBLEND_ALPHA)
+                       switch(blendmode)
+                       {
+                       case PBLEND_ALPHA:
                                GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-                       else if (blendmode == PBLEND_ADD)
+                               break;
+                       case PBLEND_ADD:
                                GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
-                       else //if (blendmode == PBLEND_MOD)
+                               break;
+                       case PBLEND_MOD:
                                GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
+                               break;
+                       }
                }
                if (texture != particletexture[p->texnum].texture)
                {
-                       if (batchcount > 0)
-                               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
-                       batchcount = 0;
-                       batchstart = surfacelistindex;
                        texture = particletexture[p->texnum].texture;
                        R_Mesh_TexBind(0, R_GetTexture(texture));
                }
 
-               batchcount++;
-       }
-       if (batchcount > 0)
-               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
-       GL_LockArrays(0, 0);
-}
-
-void R_DrawDecals (void)
-{
-       int i;
-       const decal_t *d;
-
-       // LordHavoc: early out conditions
-       if ((!cl.num_decals) || (!r_drawdecals.integer))
-               return;
-
-       // LordHavoc: only render if not too close
-       for (i = 0, d = cl.decals;i < cl.num_decals;i++, d++)
-       {
-               if (d->typeindex)
+               // iterate until we find a change in settings
+               batchstart = surfacelistindex++;
+               for (;surfacelistindex < numsurfaces;surfacelistindex++)
                {
-                       r_refdef.stats.decals++;
-                       R_MeshQueue_AddTransparent(d->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
+                       p = cl.particles + surfacelist[surfacelistindex];
+                       if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
+                               break;
                }
+
+               batchcount = surfacelistindex - batchstart;
+               R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
        }
+       GL_LockArrays(0, 0);
 }
 
 void R_DrawParticles (void)
 {
-       int i;
+       int i, j, a, content;
        float minparticledist;
        particle_t *p;
+       float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
+       int hitent;
+       trace_t trace;
+       qboolean update;
+
+       frametime = bound(0, cl.time - cl.particles_updatetime, 1);
+       cl.particles_updatetime += frametime;
 
        // LordHavoc: early out conditions
        if ((!cl.num_particles) || (!r_drawparticles.integer))
                return;
 
        minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
+       gravity = frametime * cl.movevars_gravity;
+       dvel = 1+4*frametime;
+       decalfade = frametime * 255 / cl_decals_fadetime.value;
+       update = frametime > 0;
 
-       // LordHavoc: only render if not too close
+       j = 0;
        for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
-               if (p->typeindex && !p->delayedspawn && (DotProduct(p->org, r_refdef.view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM))
+       {
+               if (!p->typeindex)
+                       continue;
+
+               if (update)
+               {
+                       if (p->delayedspawn > cl.time)
+                               continue;
+                       p->delayedspawn = 0;
+
+                       content = 0;
+
+                       p->size += p->sizeincrease * frametime;
+                       p->alpha -= p->alphafade * frametime;
+
+                       if (p->alpha <= 0 || p->die <= cl.time)
+                               goto killparticle;
+
+                       if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
+                       {
+                               if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
+                               {
+                                       if (p->typeindex == pt_blood)
+                                               p->size += frametime * 8;
+                                       else
+                                               p->vel[2] -= p->gravity * gravity;
+                                       f = 1.0f - min(p->liquidfriction * frametime, 1);
+                                       VectorScale(p->vel, f, p->vel);
+                               }
+                               else
+                               {
+                                       p->vel[2] -= p->gravity * gravity;
+                                       if (p->airfriction)
+                                       {
+                                               f = 1.0f - min(p->airfriction * frametime, 1);
+                                               VectorScale(p->vel, f, p->vel);
+                                       }
+                               }
+
+                               VectorCopy(p->org, oldorg);
+                               VectorMA(p->org, frametime, p->vel, p->org);
+                               if (p->bounce && cl.time >= p->delayedcollisions)
+                               {
+                                       trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
+                                       // if the trace started in or hit something of SUPERCONTENTS_NODROP
+                                       // or if the trace hit something flagged as NOIMPACT
+                                       // then remove the particle
+                                       if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
+                                               goto killparticle;
+                                       VectorCopy(trace.endpos, p->org);
+                                       // react if the particle hit something
+                                       if (trace.fraction < 1)
+                                       {
+                                               VectorCopy(trace.endpos, p->org);
+                                               if (p->typeindex == pt_rain)
+                                               {
+                                                       // raindrop - splash on solid/water/slime/lava
+                                                       int count;
+                                                       // convert from a raindrop particle to a rainsplash decal
+                                                       VectorCopy(trace.plane.normal, p->vel);
+                                                       VectorAdd(p->org, p->vel, p->org);
+                                                       p->typeindex = pt_raindecal;
+                                                       p->texnum = tex_rainsplash;
+                                                       p->time2 = cl.time;
+                                                       p->alphafade = p->alpha / 0.4;
+                                                       p->bounce = 0;
+                                                       p->airfriction = 0;
+                                                       p->liquidfriction = 0;
+                                                       p->gravity = 0;
+                                                       p->size *= 1.0f;
+                                                       p->sizeincrease = p->size * 20;
+                                                       count = (int)lhrandom(1, 10);
+                                                       while(count--)
+                                                               CL_NewParticle(pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
+                                                       continue;
+                                               }
+                                               else if (p->typeindex == pt_blood)
+                                               {
+                                                       // blood - splash on solid
+                                                       if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
+                                                               goto killparticle;
+                                                       if (cl_stainmaps.integer)
+                                                               R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
+                                                       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);
+                                                       }
+                                                       goto killparticle;
+                                               }
+                                               else if (p->bounce < 0)
+                                               {
+                                                       // bounce -1 means remove on impact
+                                                       goto killparticle;
+                                               }
+                                               else
+                                               {
+                                                       // anything else - bounce off solid
+                                                       dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
+                                                       VectorMA(p->vel, dist, trace.plane.normal, p->vel);
+                                                       if (DotProduct(p->vel, p->vel) < 0.03)
+                                                               VectorClear(p->vel);
+                                               }
+                                       }
+                               }
+                       }
+
+                       if (p->typeindex != pt_static)
+                       {
+                               switch (p->typeindex)
+                               {
+                               case pt_entityparticle:
+                                       // particle that removes itself after one rendered frame
+                                       if (p->time2)
+                                               goto killparticle;
+                                       else
+                                               p->time2 = 1;
+                                       break;
+                               case pt_blood:
+                                       a = CL_PointSuperContents(p->org);
+                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
+                                               goto killparticle;
+                                       break;
+                               case pt_bubble:
+                                       a = CL_PointSuperContents(p->org);
+                                       if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
+                                               goto killparticle;
+                                       break;
+                               case pt_rain:
+                                       a = CL_PointSuperContents(p->org);
+                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
+                                               goto killparticle;
+                                       break;
+                               case pt_snow:
+                                       if (cl.time > p->time2)
+                                       {
+                                               // snow flutter
+                                               p->time2 = cl.time + (rand() & 3) * 0.1;
+                                               p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
+                                               p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
+                                       }
+                                       a = CL_PointSuperContents(p->org);
+                                       if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
+                                               goto killparticle;
+                                       break;
+                               default:
+                                       break;
+                               }
+                       }
+               }
+               else if (p->delayedspawn)
+                       continue;
+
+               // don't render particles too close to the view (they chew fillrate)
+               // also don't render particles behind the view (useless)
+               // further checks to cull to the frustum would be too slow here
+               if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM)
                        R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
+
+               continue;
+killparticle:
+               p->typeindex = 0;
+               if (cl.free_particle > i)
+                       cl.free_particle = i;
+       }
+
+       // reduce cl.num_particles if possible
+       while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
+               cl.num_particles--;
 }
index a37bff2..6546773 100644 (file)
--- a/client.h
+++ b/client.h
@@ -639,37 +639,44 @@ ptype_t;
 
 typedef struct decal_s
 {
+       // fields used by rendering:  (40 bytes)
        unsigned short  typeindex;
        unsigned short  texnum;
        vec3_t                  org;
        vec3_t                  normal;
        float                   size;
        float                   alpha; // 0-255
-       float                   time2; // used for snow fluttering and decal fade
        unsigned char   color[4];
+
+       // fields not used by rendering: (36 bytes in 32bit, 40 bytes in 64bit)
+       float                   time2; // used for decal fade
        unsigned int    owner; // decal stuck to this entity
        model_t                 *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive)
        vec3_t                  relativeorigin; // decal at this location in entity's coordinate space
        vec3_t                  relativenormal; // decal oriented this way relative to entity's coordinate space
+
 }
 decal_t;
 
 typedef struct particle_s
 {
+       // fields used by rendering: (40 bytes)
        unsigned short  typeindex;
        unsigned short  texnum;
        vec3_t                  org;
        vec3_t                  vel; // velocity of particle, or orientation of decal, or end point of beam
        float                   size;
-       float                   sizeincrease; // rate of size change per second
        float                   alpha; // 0-255
+       unsigned char   color[4];
+
+       // fields not used by rendering:  (40 bytes)
+       float                   sizeincrease; // rate of size change per second
        float                   alphafade; // how much alpha reduces per second
        float                   time2; // used for snow fluttering and decal fade
        float                   bounce; // how much bounce-back from a surface the particle hits (0 = no physics, 1 = stop and slide, 2 = keep bouncing forever, 1.5 is typical)
        float                   gravity; // how much gravity affects this particle (1.0 = normal gravity, 0.0 = none)
        float                   airfriction; // how much air friction affects this object (objects with a low mass/size ratio tend to get more air friction)
        float                   liquidfriction; // how much liquid friction affects this object (objects with a low mass/size ratio tend to get more liquid friction)
-       unsigned char   color[4];
        float                   delayedcollisions; // time that p->bounce becomes active
        float                   delayedspawn; // time that particle appears and begins moving
        float                   die; // time when this particle should be removed, regardless of alpha
@@ -952,6 +959,8 @@ typedef struct client_state_s
        int num_decals;
        int num_showlmps;
 
+       double particles_updatetime;
+       double decals_updatetime;
        int free_particle;
        int free_decal;
 
@@ -985,11 +994,14 @@ typedef struct client_state_s
        // use cl.scores[cl.playerentity-1].qw_spectator instead
        //qboolean qw_spectator;
 
+       // last time an input packet was sent
+       double lastpackettime;
+
        // movement parameters for client prediction
        float movevars_wallfriction;
        float movevars_waterfriction;
        float movevars_friction;
-       float movevars_ticrate;
+       float movevars_packetinterval; // in game time (cl.time), not realtime
        float movevars_timescale;
        float movevars_gravity;
        float movevars_stopspeed;
@@ -1300,9 +1312,6 @@ void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, in
 void CL_EntityParticles (const entity_t *ent);
 void CL_ParticleExplosion (const vec3_t org);
 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength);
-void CL_MoveDecals(void);
-void CL_MoveParticles(void);
-void R_MoveExplosions(void);
 void R_NewExplosion(const vec3_t org);
 
 void Debug_PolygonBegin(const char *picname, int flags, qboolean draw2d, float linewidth);
index 54f81fa..27f16c1 100755 (executable)
--- a/netconn.c
+++ b/netconn.c
@@ -567,18 +567,18 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers
                        sendreliable = true;
                }
                // outgoing unreliable packet number, and outgoing reliable packet number (0 or 1)
-               *((int *)(sendbuffer + 0)) = LittleLong((unsigned int)conn->qw.outgoing_sequence | ((unsigned int)sendreliable<<31));
+               *((int *)(sendbuffer + 0)) = LittleLong((unsigned int)conn->outgoing_unreliable_sequence | ((unsigned int)sendreliable<<31));
                // last received unreliable packet number, and last received reliable packet number (0 or 1)
                *((int *)(sendbuffer + 4)) = LittleLong((unsigned int)conn->qw.incoming_sequence | ((unsigned int)conn->qw.incoming_reliable_sequence<<31));
                packetLen = 8;
-               conn->qw.outgoing_sequence++;
+               conn->outgoing_unreliable_sequence++;
                // client sends qport in every packet
                if (conn == cls.netcon)
                {
                        *((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport);
                        packetLen += 2;
                        // also update cls.qw_outgoing_sequence
-                       cls.qw_outgoing_sequence = conn->qw.outgoing_sequence;
+                       cls.qw_outgoing_sequence = conn->outgoing_unreliable_sequence;
                }
                if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400)
                {
@@ -594,7 +594,7 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers
                        conn->outgoing_reliablesize[conn->outgoing_packetcounter] += conn->sendMessageLength;
                        memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength);
                        packetLen += conn->sendMessageLength;
-                       conn->qw.last_reliable_sequence = conn->qw.outgoing_sequence;
+                       conn->qw.last_reliable_sequence = conn->outgoing_unreliable_sequence;
                }
 
                // add the unreliable message if possible
@@ -715,10 +715,10 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers
 
                        header = (unsigned int *)sendbuffer;
                        header[0] = BigLong(packetLen | NETFLAG_UNRELIABLE);
-                       header[1] = BigLong(conn->nq.unreliableSendSequence);
+                       header[1] = BigLong(conn->outgoing_unreliable_sequence);
                        memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize);
 
-                       conn->nq.unreliableSendSequence++;
+                       conn->outgoing_unreliable_sequence++;
 
                        conn->outgoing_unreliablesize[conn->outgoing_packetcounter] += packetLen;
 
@@ -1208,7 +1208,6 @@ void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peer
        cls.signon = 0;                         // need all the signon messages before playing
        cls.protocol = initialprotocol;
        // reset move sequence numbering on this new connection
-       cls.movesequence = 1;
        cls.servermovesequence = 0;
        if (cls.protocol == PROTOCOL_QUAKEWORLD)
                Cmd_ForwardStringToServer("new");
@@ -2657,7 +2656,7 @@ static void Net_Heartbeat_f(void)
 void PrintStats(netconn_t *conn)
 {
        if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD))
-               Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->qw.outgoing_sequence, conn->qw.incoming_sequence);
+               Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->outgoing_unreliable_sequence, conn->qw.incoming_sequence);
        else
                Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence);
 }
index 2ac3133..a755d28 100755 (executable)
--- a/netconn.h
+++ b/netconn.h
@@ -142,11 +142,13 @@ typedef struct netconn_s
        int receiveMessageLength;
        unsigned char receiveMessage[NET_MAXMESSAGE];
 
+       // used by both NQ and QW protocols
+       unsigned int outgoing_unreliable_sequence;
+
        struct netconn_nq_s
        {
                unsigned int ackSequence;
                unsigned int sendSequence;
-               unsigned int unreliableSendSequence;
 
                unsigned int receiveSequence;
                unsigned int unreliableReceiveSequence;
@@ -176,7 +178,6 @@ typedef struct netconn_s
 
                int                     incoming_reliable_sequence;             // single bit, maintained local
 
-               int                     outgoing_sequence;
                int                     reliable_sequence;                      // single bit
                int                     last_reliable_sequence;         // sequence number of last send
        }
index 1d822ec..410fa67 100644 (file)
@@ -250,25 +250,23 @@ static void R_MoveExplosion(explosion_t *e)
        }
 }
 
-
-void R_MoveExplosions(void)
-{
-       int i;
-       for (i = 0;i < numexplosions;i++)
-               if (explosion[i].alpha)
-                       R_MoveExplosion(&explosion[i]);
-       while (numexplosions > 0 && explosion[i-1].alpha <= 0)
-               numexplosions--;
-}
-
 void R_DrawExplosions(void)
 {
        int i;
 
        if (!r_drawexplosions.integer)
                return;
+
        for (i = 0;i < numexplosions;i++)
+       {
                if (explosion[i].alpha)
-                       R_MeshQueue_AddTransparent(explosion[i].origin, R_DrawExplosion_TransparentCallback, NULL, i, NULL);
+               {
+                       R_MoveExplosion(&explosion[i]);
+                       if (explosion[i].alpha)
+                               R_MeshQueue_AddTransparent(explosion[i].origin, R_DrawExplosion_TransparentCallback, NULL, i, NULL);
+               }
+       }
+       while (numexplosions > 0 && explosion[i-1].alpha <= 0)
+               numexplosions--;
 }