todo item done: fixed con_notify cvar (limits number of displayed notify lines)
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #ifdef WORKINGLQUAKE
24 #define lhrandom(MIN,MAX) ((rand() & 32767) * (((MAX)-(MIN)) * (1.0f / 32767.0f)) + (MIN))
25 #define NUMVERTEXNORMALS        162
26 siextern float r_avertexnormals[NUMVERTEXNORMALS][3];
27 #define m_bytenormals r_avertexnormals
28 #define VectorNormalizeFast VectorNormalize
29 #define CL_PointQ1Contents(v) (Mod_PointInLeaf(v,cl.worldmodel)->contents)
30 typedef unsigned char qbyte;
31 #define cl_stainmaps.integer 0
32 void R_Stain (vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2)
33 {
34 }
35 #define CL_EntityParticles R_EntityParticles
36 #define CL_ReadPointFile_f R_ReadPointFile_f
37 #define CL_ParseParticleEffect R_ParseParticleEffect
38 #define CL_ParticleExplosion R_ParticleExplosion
39 #define CL_ParticleExplosion2 R_ParticleExplosion2
40 #define CL_BlobExplosion R_BlobExplosion
41 #define CL_RunParticleEffect R_RunParticleEffect
42 #define CL_LavaSplash R_LavaSplash
43 #define CL_RocketTrail2 R_RocketTrail2
44 void R_CalcBeam_Vertex3f (float *vert, vec3_t org1, vec3_t org2, float width)
45 {
46         vec3_t right1, right2, diff, normal;
47
48         VectorSubtract (org2, org1, normal);
49         VectorNormalizeFast (normal);
50
51         // calculate 'right' vector for start
52         VectorSubtract (r_vieworigin, org1, diff);
53         VectorNormalizeFast (diff);
54         CrossProduct (normal, diff, right1);
55
56         // calculate 'right' vector for end
57         VectorSubtract (r_vieworigin, org2, diff);
58         VectorNormalizeFast (diff);
59         CrossProduct (normal, diff, right2);
60
61         vert[ 0] = org1[0] + width * right1[0];
62         vert[ 1] = org1[1] + width * right1[1];
63         vert[ 2] = org1[2] + width * right1[2];
64         vert[ 3] = org1[0] - width * right1[0];
65         vert[ 4] = org1[1] - width * right1[1];
66         vert[ 5] = org1[2] - width * right1[2];
67         vert[ 6] = org2[0] - width * right2[0];
68         vert[ 7] = org2[1] - width * right2[1];
69         vert[ 8] = org2[2] - width * right2[2];
70         vert[ 9] = org2[0] + width * right2[0];
71         vert[10] = org2[1] + width * right2[1];
72         vert[11] = org2[2] + width * right2[2];
73 }
74 void fractalnoise(qbyte *noise, int size, int startgrid)
75 {
76         int x, y, g, g2, amplitude, min, max, size1 = size - 1, sizepower, gridpower;
77         int *noisebuf;
78 #define n(x,y) noisebuf[((y)&size1)*size+((x)&size1)]
79
80         for (sizepower = 0;(1 << sizepower) < size;sizepower++);
81         if (size != (1 << sizepower))
82                 Sys_Error("fractalnoise: size must be power of 2\n");
83
84         for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++);
85         if (startgrid != (1 << gridpower))
86                 Sys_Error("fractalnoise: grid must be power of 2\n");
87
88         startgrid = bound(0, startgrid, size);
89
90         amplitude = 0xFFFF; // this gets halved before use
91         noisebuf = malloc(size*size*sizeof(int));
92         memset(noisebuf, 0, size*size*sizeof(int));
93
94         for (g2 = startgrid;g2;g2 >>= 1)
95         {
96                 // brownian motion (at every smaller level there is random behavior)
97                 amplitude >>= 1;
98                 for (y = 0;y < size;y += g2)
99                         for (x = 0;x < size;x += g2)
100                                 n(x,y) += (rand()&amplitude);
101
102                 g = g2 >> 1;
103                 if (g)
104                 {
105                         // subdivide, diamond-square algorithm (really this has little to do with squares)
106                         // diamond
107                         for (y = 0;y < size;y += g2)
108                                 for (x = 0;x < size;x += g2)
109                                         n(x+g,y+g) = (n(x,y) + n(x+g2,y) + n(x,y+g2) + n(x+g2,y+g2)) >> 2;
110                         // square
111                         for (y = 0;y < size;y += g2)
112                                 for (x = 0;x < size;x += g2)
113                                 {
114                                         n(x+g,y) = (n(x,y) + n(x+g2,y) + n(x+g,y-g) + n(x+g,y+g)) >> 2;
115                                         n(x,y+g) = (n(x,y) + n(x,y+g2) + n(x-g,y+g) + n(x+g,y+g)) >> 2;
116                                 }
117                 }
118         }
119         // find range of noise values
120         min = max = 0;
121         for (y = 0;y < size;y++)
122                 for (x = 0;x < size;x++)
123                 {
124                         if (n(x,y) < min) min = n(x,y);
125                         if (n(x,y) > max) max = n(x,y);
126                 }
127         max -= min;
128         max++;
129         // normalize noise and copy to output
130         for (y = 0;y < size;y++)
131                 for (x = 0;x < size;x++)
132                         *noise++ = (qbyte) (((n(x,y) - min) * 256) / max);
133         free(noisebuf);
134 #undef n
135 }
136 void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up)
137 {
138         float d;
139
140         right[0] = forward[2];
141         right[1] = -forward[0];
142         right[2] = forward[1];
143
144         d = DotProduct(forward, right);
145         right[0] -= d * forward[0];
146         right[1] -= d * forward[1];
147         right[2] -= d * forward[2];
148         VectorNormalizeFast(right);
149         CrossProduct(right, forward, up);
150 }
151 #if QW
152 #include "pmove.h"
153 extern qboolean PM_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace);
154 #endif
155 float CL_TraceLine (vec3_t start, vec3_t end, vec3_t impact, vec3_t normal, int hitbmodels, void **hitent, int hitsupercontentsmask)
156 {
157 #if QW
158         pmtrace_t trace;
159 #else
160         trace_t trace;
161 #endif
162         memset (&trace, 0, sizeof(trace));
163         trace.fraction = 1;
164         VectorCopy (end, trace.endpos);
165 #if QW
166         PM_RecursiveHullCheck (cl.model_precache[1]->hulls, 0, 0, 1, start, end, &trace);
167 #else
168         RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace);
169 #endif
170         VectorCopy(trace.endpos, impact);
171         VectorCopy(trace.plane.normal, normal);
172         return trace.fraction;
173 }
174 #else
175 #include "cl_collision.h"
176 #endif
177
178 #define MAX_PARTICLES                   32768   // default max # of particles at one time
179 #define ABSOLUTE_MIN_PARTICLES  512             // no fewer than this no matter what's on the command line
180
181 typedef enum
182 {
183         pt_static, pt_rain, pt_bubble, pt_blood, pt_grow, pt_decal, pt_decalfade
184 }
185 ptype_t;
186
187 #define PARTICLE_INVALID 0
188 #define PARTICLE_BILLBOARD 1
189 #define PARTICLE_SPARK 2
190 #define PARTICLE_ORIENTED_DOUBLESIDED 3
191 #define PARTICLE_BEAM 4
192
193 #define PBLEND_ALPHA 0
194 #define PBLEND_ADD 1
195 #define PBLEND_MOD 2
196
197 typedef struct particle_s
198 {
199         ptype_t         type;
200         int                     orientation;
201         int                     texnum;
202         int                     blendmode;
203         vec3_t          org;
204         vec3_t          vel;
205         float           die;
206         float           scalex;
207         float           scaley;
208         float           alpha; // 0-255
209         float           alphafade; // how much alpha reduces per second
210         float           time2; // used for various things (snow fluttering, for example)
211         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)
212         float           gravity; // how much gravity affects this particle (1.0 = normal gravity, 0.0 = none)
213         vec3_t          oldorg;
214         vec3_t          vel2; // used for snow fluttering (base velocity, wind for instance)
215         float           friction; // how much air friction affects this object (objects with a low mass/size ratio tend to get more air friction)
216         float           pressure; // if non-zero, apply pressure to other particles
217         qbyte           color[4];
218 #ifndef WORKINGLQUAKE
219         entity_render_t *owner; // decal stuck to this entity
220         model_t         *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive)
221         vec3_t          relativeorigin; // decal at this location in entity's coordinate space
222         vec3_t          relativedirection; // decal oriented this way relative to entity's coordinate space
223 #endif
224 }
225 particle_t;
226
227 static int particlepalette[256] =
228 {
229         0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b,
230         0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb,
231         0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b,
232         0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23,
233         0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767,
234         0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb,
235         0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07,
236         0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f,
237         0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000,
238         0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000,
239         0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307,
240         0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723,
241         0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b,
242         0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b,
243         0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733,
244         0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397,
245         0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b,
246         0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707,
247         0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b,
248         0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707,
249         0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353,
250         0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07,
251         0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f,
252         0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07,
253         0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307,
254         0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700,
255         0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f,
256         0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f,
257         0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b,
258         0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b,
259         0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000,
260         0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53
261 };
262
263 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
264
265 // texture numbers in particle font
266 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
267 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
268 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
269 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
270 static const int tex_rainsplash[16] = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
271 static const int tex_particle = 63;
272 static const int tex_bubble = 62;
273 static const int tex_raindrop = 61;
274 static const int tex_beam = 60;
275
276 static int                      cl_maxparticles;
277 static int                      cl_numparticles;
278 static particle_t       *particles;
279 static particle_t       **freeparticles; // list used only in compacting particles array
280
281 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1"};
282 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1"};
283 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1"};
284 cvar_t cl_particles_bloodshowers = {CVAR_SAVE, "cl_particles_bloodshowers", "1"};
285 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1"};
286 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5"};
287 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1"};
288 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1"};
289 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1"};
290 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5"};
291 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55"};
292 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1"};
293 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1"};
294 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0"};
295 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0"};
296 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20"};
297
298 #ifndef WORKINGLQUAKE
299 static mempool_t *cl_part_mempool;
300 #endif
301
302 void CL_Particles_Clear(void)
303 {
304         cl_numparticles = 0;
305 }
306
307 /*
308 ===============
309 CL_InitParticles
310 ===============
311 */
312 void CL_ReadPointFile_f (void);
313 void CL_Particles_Init (void)
314 {
315         int             i;
316
317         i = COM_CheckParm ("-particles");
318
319         if (i && i < com_argc - 1)
320         {
321                 cl_maxparticles = (int)(atoi(com_argv[i+1]));
322                 if (cl_maxparticles < ABSOLUTE_MIN_PARTICLES)
323                         cl_maxparticles = ABSOLUTE_MIN_PARTICLES;
324         }
325         else
326                 cl_maxparticles = MAX_PARTICLES;
327
328         Cmd_AddCommand ("pointfile", CL_ReadPointFile_f);
329
330         Cvar_RegisterVariable (&cl_particles);
331         Cvar_RegisterVariable (&cl_particles_quality);
332         Cvar_RegisterVariable (&cl_particles_size);
333         Cvar_RegisterVariable (&cl_particles_bloodshowers);
334         Cvar_RegisterVariable (&cl_particles_blood);
335         Cvar_RegisterVariable (&cl_particles_blood_alpha);
336         Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
337         Cvar_RegisterVariable (&cl_particles_bulletimpacts);
338         Cvar_RegisterVariable (&cl_particles_smoke);
339         Cvar_RegisterVariable (&cl_particles_smoke_alpha);
340         Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
341         Cvar_RegisterVariable (&cl_particles_sparks);
342         Cvar_RegisterVariable (&cl_particles_bubbles);
343         Cvar_RegisterVariable (&cl_decals);
344         Cvar_RegisterVariable (&cl_decals_time);
345         Cvar_RegisterVariable (&cl_decals_fadetime);
346
347 #ifdef WORKINGLQUAKE
348         particles = (particle_t *) Hunk_AllocName(cl_maxparticles * sizeof(particle_t), "particles");
349         freeparticles = (void *) Hunk_AllocName(cl_maxparticles * sizeof(particle_t *), "particles");
350 #else
351         cl_part_mempool = Mem_AllocPool("CL_Part");
352         particles = (particle_t *) Mem_Alloc(cl_part_mempool, cl_maxparticles * sizeof(particle_t));
353         freeparticles = (void *) Mem_Alloc(cl_part_mempool, cl_maxparticles * sizeof(particle_t *));
354 #endif
355         cl_numparticles = 0;
356 }
357
358 particle_t *particle(int ptype, int porientation, int pcolor1, int pcolor2, int ptex, int plight, int pblendmode, float pscalex, float pscaley, float palpha, float palphafade, float ptime, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float ptime2, float pvx2, float pvy2, float pvz2, float pfriction, float ppressure)
359 {
360         if (cl_numparticles < cl_maxparticles)
361         {
362                 particle_t *part;
363                 int ptempcolor, ptempcolor2, pcr1, pcg1, pcb1, pcr2, pcg2, pcb2;
364                 ptempcolor = (pcolor1);
365                 ptempcolor2 = (pcolor2);
366                 pcr2 = ((ptempcolor2) >> 16) & 0xFF;
367                 pcg2 = ((ptempcolor2) >> 8) & 0xFF;
368                 pcb2 = (ptempcolor2) & 0xFF;
369                 if (ptempcolor != ptempcolor2)
370                 {
371                         pcr1 = ((ptempcolor) >> 16) & 0xFF;
372                         pcg1 = ((ptempcolor) >> 8) & 0xFF;
373                         pcb1 = (ptempcolor) & 0xFF;
374                         ptempcolor = rand() & 0xFF;
375                         pcr2 = (((pcr2 - pcr1) * ptempcolor) >> 8) + pcr1;
376                         pcg2 = (((pcg2 - pcg1) * ptempcolor) >> 8) + pcg1;
377                         pcb2 = (((pcb2 - pcb1) * ptempcolor) >> 8) + pcb1;
378                 }
379                 part = &particles[cl_numparticles++];
380                 memset(part, 0, sizeof(*part));
381                 part->type = (ptype);
382                 part->color[0] = pcr2;
383                 part->color[1] = pcg2;
384                 part->color[2] = pcb2;
385                 part->color[3] = 0xFF;
386                 part->orientation = porientation;
387                 part->texnum = ptex;
388                 part->blendmode = pblendmode;
389                 part->scalex = (pscalex);
390                 part->scaley = (pscaley);
391                 part->alpha = (palpha);
392                 part->alphafade = (palphafade);
393                 part->die = cl.time + (ptime);
394                 part->gravity = (pgravity);
395                 part->bounce = (pbounce);
396                 part->org[0] = (px);
397                 part->org[1] = (py);
398                 part->org[2] = (pz);
399                 part->vel[0] = (pvx);
400                 part->vel[1] = (pvy);
401                 part->vel[2] = (pvz);
402                 part->time2 = (ptime2);
403                 part->vel2[0] = (pvx2);
404                 part->vel2[1] = (pvy2);
405                 part->vel2[2] = (pvz2);
406                 part->friction = (pfriction);
407                 part->pressure = (ppressure);
408                 return part;
409         }
410         return NULL;
411 }
412
413 void CL_SpawnDecalParticleForSurface(void *hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
414 {
415         particle_t *p;
416         if (!cl_decals.integer)
417                 return;
418         p = particle(pt_decal, PARTICLE_ORIENTED_DOUBLESIDED, color1, color2, texnum, false, PBLEND_MOD, size, size, alpha, 0, cl_decals_time.value + cl_decals_fadetime.value, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], 0, 0, 0, cl.time + cl_decals_time.value, normal[0], normal[1], normal[2], 0, 0);
419 #ifndef WORKINGLQUAKE
420         if (p)
421         {
422                 p->owner = hitent;
423                 p->ownermodel = p->owner->model;
424                 Matrix4x4_Transform(&p->owner->inversematrix, org, p->relativeorigin);
425                 Matrix4x4_Transform3x3(&p->owner->inversematrix, normal, p->relativedirection);
426                 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
427         }
428 #endif
429 }
430
431 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
432 {
433         int i;
434         float bestfrac, bestorg[3], bestnormal[3];
435         float frac, v[3], normal[3], org2[3];
436 #ifdef WORKINGLQUAKE
437         void *besthitent = NULL, *hitent;
438 #else
439         entity_render_t *besthitent = NULL, *hitent;
440 #endif
441         bestfrac = 10;
442         for (i = 0;i < 32;i++)
443         {
444                 VectorRandom(org2);
445                 VectorMA(org, maxdist, org2, org2);
446                 frac = CL_TraceLine(org, org2, v, normal, true, &hitent, SUPERCONTENTS_SOLID);
447                 if (bestfrac > frac)
448                 {
449                         bestfrac = frac;
450                         besthitent = hitent;
451                         VectorCopy(v, bestorg);
452                         VectorCopy(normal, bestnormal);
453                 }
454         }
455         if (bestfrac < 1)
456                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
457 }
458
459 /*
460 ===============
461 CL_EntityParticles
462 ===============
463 */
464 void CL_EntityParticles (entity_t *ent)
465 {
466         int                     i;
467         float           angle;
468         float           sp, sy, cp, cy;
469         vec3_t          forward;
470         float           dist;
471         float           beamlength;
472         static vec3_t avelocities[NUMVERTEXNORMALS];
473         if (!cl_particles.integer) return;
474
475         dist = 64;
476         beamlength = 16;
477
478         if (!avelocities[0][0])
479                 for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
480                         avelocities[0][i] = (rand()&255) * 0.01;
481
482         for (i=0 ; i<NUMVERTEXNORMALS ; i++)
483         {
484                 angle = cl.time * avelocities[i][0];
485                 sy = sin(angle);
486                 cy = cos(angle);
487                 angle = cl.time * avelocities[i][1];
488                 sp = sin(angle);
489                 cp = cos(angle);
490
491                 forward[0] = cp*cy;
492                 forward[1] = cp*sy;
493                 forward[2] = -sp;
494
495 #ifdef WORKINGLQUAKE
496                 particle(pt_static, PARTICLE_BILLBOARD, particlepalette[0x6f], particlepalette[0x6f], tex_particle, false, PBLEND_ADD, 2, 2, 255, 0, 0, 0, 0, ent->origin[0] + m_bytenormals[i][0]*dist + forward[0]*beamlength, ent->origin[1] + m_bytenormals[i][1]*dist + forward[1]*beamlength, ent->origin[2] + m_bytenormals[i][2]*dist + forward[2]*beamlength, 0, 0, 0, 0, 0, 0, 0, 0, 0);
497 #else
498                 particle(pt_static, PARTICLE_BILLBOARD, particlepalette[0x6f], particlepalette[0x6f], tex_particle, false, PBLEND_ADD, 2, 2, 255, 0, 0, 0, 0, ent->render.origin[0] + m_bytenormals[i][0]*dist + forward[0]*beamlength, ent->render.origin[1] + m_bytenormals[i][1]*dist + forward[1]*beamlength, ent->render.origin[2] + m_bytenormals[i][2]*dist + forward[2]*beamlength, 0, 0, 0, 0, 0, 0, 0, 0, 0);
499 #endif
500         }
501 }
502
503
504 void CL_ReadPointFile_f (void)
505 {
506         vec3_t org, leakorg;
507         int r, c, s;
508         char *pointfile = NULL, *pointfilepos, *t, tchar;
509         char name[MAX_OSPATH];
510
511         if (!cl.worldmodel)
512                 return;
513
514         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
515         strlcat (name, ".pts", sizeof (name));
516 #if WORKINGLQUAKE
517         pointfile = COM_LoadTempFile (name);
518 #else
519         pointfile = FS_LoadFile(name, true);
520 #endif
521         if (!pointfile)
522         {
523                 Con_Printf ("Could not open %s\n", name);
524                 return;
525         }
526
527         Con_Printf ("Reading %s...\n", name);
528         c = 0;
529         s = 0;
530         pointfilepos = pointfile;
531         while (*pointfilepos)
532         {
533                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
534                         pointfilepos++;
535                 if (!*pointfilepos)
536                         break;
537                 t = pointfilepos;
538                 while (*t && *t != '\n' && *t != '\r')
539                         t++;
540                 tchar = *t;
541                 *t = 0;
542                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
543                 *t = tchar;
544                 pointfilepos = t;
545                 if (r != 3)
546                         break;
547                 if (c == 0)
548                         VectorCopy(org, leakorg);
549                 c++;
550
551                 if (cl_numparticles < cl_maxparticles - 3)
552                 {
553                         s++;
554                         particle(pt_static, PARTICLE_BILLBOARD, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, false, PBLEND_ALPHA, 2, 2, 255, 0, 99999, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, 0, 0);
555                 }
556         }
557 #ifndef WORKINGLQUAKE
558         Mem_Free(pointfile);
559 #endif
560         VectorCopy(leakorg, org);
561         Con_Printf ("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
562
563         particle(pt_static, PARTICLE_BEAM, 0xFF0000, 0xFF0000, tex_beam, false, PBLEND_ALPHA, 64, 64, 255, 0, 99999, 0, 0, org[0] - 4096, org[1], org[2], 0, 0, 0, 0, org[0] + 4096, org[1], org[2], 0, 0);
564         particle(pt_static, PARTICLE_BEAM, 0x00FF00, 0x00FF00, tex_beam, false, PBLEND_ALPHA, 64, 64, 255, 0, 99999, 0, 0, org[0], org[1] - 4096, org[2], 0, 0, 0, 0, org[0], org[1] + 4096, org[2], 0, 0);
565         particle(pt_static, PARTICLE_BEAM, 0x0000FF, 0x0000FF, tex_beam, false, PBLEND_ALPHA, 64, 64, 255, 0, 99999, 0, 0, org[0], org[1], org[2] - 4096, 0, 0, 0, 0, org[0], org[1], org[2] + 4096, 0, 0);
566 }
567
568 /*
569 ===============
570 CL_ParseParticleEffect
571
572 Parse an effect out of the server message
573 ===============
574 */
575 void CL_ParseParticleEffect (void)
576 {
577         vec3_t org, dir;
578         int i, count, msgcount, color;
579
580         for (i=0 ; i<3 ; i++)
581                 org[i] = MSG_ReadCoord ();
582         for (i=0 ; i<3 ; i++)
583                 dir[i] = MSG_ReadChar () * (1.0/16);
584         msgcount = MSG_ReadByte ();
585         color = MSG_ReadByte ();
586
587         if (msgcount == 255)
588                 count = 1024;
589         else
590                 count = msgcount;
591
592         if (cl_particles_blood_bloodhack.integer)
593         {
594                 if (color == 73)
595                 {
596                         // regular blood
597                         CL_BloodPuff(org, dir, count / 2);
598                         return;
599                 }
600                 if (color == 225)
601                 {
602                         // lightning blood
603                         CL_BloodPuff(org, dir, count / 2);
604                         return;
605                 }
606         }
607         CL_RunParticleEffect (org, dir, color, count);
608 }
609
610 /*
611 ===============
612 CL_ParticleExplosion
613
614 ===============
615 */
616 void CL_ParticleExplosion (vec3_t org)
617 {
618         int i, k;
619         //vec3_t v;
620         //vec3_t v2;
621         if (cl_stainmaps.integer)
622                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
623         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
624
625         i = CL_PointQ1Contents(org);
626         if ((i == CONTENTS_SLIME || i == CONTENTS_WATER) && cl_particles.integer && cl_particles_bubbles.integer)
627         {
628                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
629                         particle(pt_bubble, PARTICLE_BILLBOARD, 0x404040, 0x808080, tex_bubble, false, PBLEND_ADD, 2, 2, (1.0f / cl_particles_quality.value) * lhrandom(128, 255), (1.0f / cl_particles_quality.value) * 256, 9999, -0.25, 1.5, org[0] + lhrandom(-16, 16), org[1] + lhrandom(-16, 16), org[2] + lhrandom(-16, 16), lhrandom(-96, 96), lhrandom(-96, 96), lhrandom(-96, 96), 0, 0, 0, 0, (1.0 / 16.0), 0);
630         }
631         else
632         {
633                 /*
634                 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
635                 // smoke puff
636                 if (cl_particles.integer && cl_particles_smoke.integer)
637                 {
638                         for (i = 0;i < 64;i++)
639                         {
640 #ifdef WORKINGLQUAKE
641                                 v2[0] = lhrandom(-64, 64);
642                                 v2[1] = lhrandom(-64, 64);
643                                 v2[2] = lhrandom(-8, 24);
644 #else
645                                 for (k = 0;k < 16;k++)
646                                 {
647                                         v[0] = org[0] + lhrandom(-64, 64);
648                                         v[1] = org[1] + lhrandom(-64, 64);
649                                         v[2] = org[2] + lhrandom(-8, 24);
650                                         if (CL_TraceLine(org, v, v2, NULL, true, NULL, SUPERCONTENTS_SOLID) >= 0.1)
651                                                 break;
652                                 }
653                                 VectorSubtract(v2, org, v2);
654 #endif
655                                 VectorScale(v2, 2.0f, v2);
656                                 particle(pt_static, PARTICLE_BILLBOARD, 0x101010, 0x202020, tex_smoke[rand()&7], true, PBLEND_ADD, 12, 12, 255, 512, 9999, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, 0, 0);
657                         }
658                 }
659                 */
660
661                 if (cl_particles.integer && cl_particles_sparks.integer)
662                 {
663                         // sparks
664                         for (i = 0;i < 256 * cl_particles_quality.value;i++)
665                         {
666                                 k = particlepalette[0x68 + (rand() & 7)];
667                                 particle(pt_static, PARTICLE_SPARK, k, k, tex_particle, false, PBLEND_ADD, 1.5f, 0.05f, (1.0f / cl_particles_quality.value) * lhrandom(0, 255), (1.0f / cl_particles_quality.value) * 512, 9999, 1, 0, org[0], org[1], org[2], lhrandom(-192, 192), lhrandom(-192, 192), lhrandom(-192, 192) + 160, 0, 0, 0, 0, 0, 0);
668                         }
669                 }
670         }
671
672         if (cl_explosions.integer)
673                 R_NewExplosion(org);
674 }
675
676 /*
677 ===============
678 CL_ParticleExplosion2
679
680 ===============
681 */
682 void CL_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength)
683 {
684         int i, k;
685         if (!cl_particles.integer) return;
686
687         for (i = 0;i < 512 * cl_particles_quality.value;i++)
688         {
689                 k = particlepalette[colorStart + (i % colorLength)];
690                 particle(pt_static, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ALPHA, 1.5, 1.5, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 384, 0.3, 0, 0, org[0] + lhrandom(-8, 8), org[1] + lhrandom(-8, 8), org[2] + lhrandom(-8, 8), lhrandom(-192, 192), lhrandom(-192, 192), lhrandom(-192, 192), 0, 0, 0, 0, 1, 0);
691         }
692 }
693
694 /*
695 ===============
696 CL_BlobExplosion
697
698 ===============
699 */
700 void CL_BlobExplosion (vec3_t org)
701 {
702         if (cl_stainmaps.integer)
703                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
704         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
705
706         if (cl_explosions.integer)
707                 R_NewExplosion(org);
708 }
709
710 /*
711 ===============
712 CL_RunParticleEffect
713
714 ===============
715 */
716 void CL_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
717 {
718         int k;
719
720         if (count == 1024)
721         {
722                 CL_ParticleExplosion(org);
723                 return;
724         }
725         if (!cl_particles.integer) return;
726         count *= cl_particles_quality.value;
727         while (count--)
728         {
729                 k = particlepalette[color + (rand()&7)];
730                 if (gamemode == GAME_GOODVSBAD2)
731                         particle(pt_static, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ALPHA, 5, 5, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 300, 9999, 0, 0, org[0] + lhrandom(-8, 8), org[1] + lhrandom(-8, 8), org[2] + lhrandom(-8, 8), lhrandom(-10, 10), lhrandom(-10, 10), lhrandom(-10, 10), 0, 0, 0, 0, 0, 0);
732                 else
733                         particle(pt_static, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ALPHA, 1, 1, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 512, 9999, 0, 0, org[0] + lhrandom(-8, 8), org[1] + lhrandom(-8, 8), org[2] + lhrandom(-8, 8), dir[0] + lhrandom(-15, 15), dir[1] + lhrandom(-15, 15), dir[2] + lhrandom(-15, 15), 0, 0, 0, 0, 0, 0);
734         }
735 }
736
737 // LordHavoc: added this for spawning sparks/dust (which have strong gravity)
738 /*
739 ===============
740 CL_SparkShower
741 ===============
742 */
743 void CL_SparkShower (vec3_t org, vec3_t dir, int count)
744 {
745         vec3_t org2, org3;
746         int k;
747
748         if (cl_stainmaps.integer)
749                 R_Stain(org, 32, 96, 96, 96, 24, 128, 128, 128, 24);
750         CL_SpawnDecalParticleForPoint(org, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
751
752         if (!cl_particles.integer) return;
753
754         if (cl_particles_bulletimpacts.integer)
755         {
756                 // smoke puff
757                 if (cl_particles_smoke.integer)
758                 {
759                         k = count * 0.25 * cl_particles_quality.value;
760                         while(k--)
761                         {
762                                 org2[0] = org[0] + 0.125f * lhrandom(-count, count);
763                                 org2[1] = org[1] + 0.125f * lhrandom(-count, count);
764                                 org2[2] = org[2] + 0.125f * lhrandom(-count, count);
765                                 CL_TraceLine(org, org2, org3, NULL, true, NULL, SUPERCONTENTS_SOLID);
766                                 particle(pt_grow, PARTICLE_BILLBOARD, 0x101010, 0x202020, tex_smoke[rand()&7], true, PBLEND_ADD, 3, 3, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 1024, 9999, -0.2, 0, org3[0], org3[1], org3[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(0, 16), 15, 0, 0, 0, 0, 0);
767                         }
768                 }
769
770                 if (cl_particles_sparks.integer)
771                 {
772                         // sparks
773                         count *= cl_particles_quality.value;
774                         while(count--)
775                         {
776                                 k = particlepalette[0x68 + (rand() & 7)];
777                                 particle(pt_static, PARTICLE_SPARK, k, k, tex_particle, false, PBLEND_ADD, 0.4f, 0.015f, (1.0f / cl_particles_quality.value) * lhrandom(64, 255), (1.0f / cl_particles_quality.value) * 512, 9999, 1, 0, org[0], org[1], org[2], lhrandom(-64, 64) + dir[0], lhrandom(-64, 64) + dir[1], lhrandom(0, 128) + dir[2], 0, 0, 0, 0, 0, 0);
778                         }
779                 }
780         }
781 }
782
783 void CL_PlasmaBurn (vec3_t org)
784 {
785         if (cl_stainmaps.integer)
786                 R_Stain(org, 48, 96, 96, 96, 32, 128, 128, 128, 32);
787         CL_SpawnDecalParticleForPoint(org, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
788 }
789
790 static float bloodcount = 0;
791 void CL_BloodPuff (vec3_t org, vec3_t vel, int count)
792 {
793         float s;
794         vec3_t org2, org3;
795         // bloodcount is used to accumulate counts too small to cause a blood particle
796         if (!cl_particles.integer) return;
797         if (!cl_particles_blood.integer) return;
798
799         s = count + 64.0f;
800         count *= 5.0f;
801         if (count > 1000)
802                 count = 1000;
803         bloodcount += count;
804         while(bloodcount > 0)
805         {
806                 org2[0] = org[0] + 0.125f * lhrandom(-bloodcount, bloodcount);
807                 org2[1] = org[1] + 0.125f * lhrandom(-bloodcount, bloodcount);
808                 org2[2] = org[2] + 0.125f * lhrandom(-bloodcount, bloodcount);
809                 CL_TraceLine(org, org2, org3, NULL, true, NULL, SUPERCONTENTS_SOLID);
810                 particle(pt_blood, PARTICLE_BILLBOARD, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], true, PBLEND_MOD, 8, 8, cl_particles_blood_alpha.value * 768 / cl_particles_quality.value, cl_particles_blood_alpha.value * 384 / cl_particles_quality.value, 9999, 0, -1, org3[0], org3[1], org3[2], vel[0] + lhrandom(-s, s), vel[1] + lhrandom(-s, s), vel[2] + lhrandom(-s, s), 0, 0, 0, 0, 1, 0);
811                 bloodcount -= 16 / cl_particles_quality.value;
812         }
813 }
814
815 void CL_BloodShower (vec3_t mins, vec3_t maxs, float velspeed, int count)
816 {
817         vec3_t org, vel, diff, center, velscale;
818         if (!cl_particles.integer) return;
819         if (!cl_particles_bloodshowers.integer) return;
820         if (!cl_particles_blood.integer) return;
821
822         VectorSubtract(maxs, mins, diff);
823         center[0] = (mins[0] + maxs[0]) * 0.5;
824         center[1] = (mins[1] + maxs[1]) * 0.5;
825         center[2] = (mins[2] + maxs[2]) * 0.5;
826         velscale[0] = velspeed * 2.0 / diff[0];
827         velscale[1] = velspeed * 2.0 / diff[1];
828         velscale[2] = velspeed * 2.0 / diff[2];
829
830         bloodcount += count * 5.0f;
831         while (bloodcount > 0)
832         {
833                 org[0] = lhrandom(mins[0], maxs[0]);
834                 org[1] = lhrandom(mins[1], maxs[1]);
835                 org[2] = lhrandom(mins[2], maxs[2]);
836                 vel[0] = (org[0] - center[0]) * velscale[0];
837                 vel[1] = (org[1] - center[1]) * velscale[1];
838                 vel[2] = (org[2] - center[2]) * velscale[2];
839                 bloodcount -= 16 / cl_particles_quality.value;
840                 particle(pt_blood, PARTICLE_BILLBOARD, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], true, PBLEND_MOD, 8, 8, cl_particles_blood_alpha.value * 768 / cl_particles_quality.value, cl_particles_blood_alpha.value * 384 / cl_particles_quality.value, 9999, 0, -1, org[0], org[1], org[2], vel[0], vel[1], vel[2], 0, 0, 0, 0, 1, 0);
841         }
842 }
843
844 void CL_ParticleCube (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int gravity, int randomvel)
845 {
846         int k;
847         float t;
848         if (!cl_particles.integer) return;
849         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
850         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
851         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
852
853         count *= cl_particles_quality.value;
854         while (count--)
855         {
856                 k = particlepalette[colorbase + (rand()&3)];
857                 particle(pt_static, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ALPHA, 2, 2, 255 / cl_particles_quality.value, 0, lhrandom(1, 2), gravity ? 1 : 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0] + lhrandom(-randomvel, randomvel), dir[1] + lhrandom(-randomvel, randomvel), dir[2] + lhrandom(-randomvel, randomvel), 0, 0, 0, 0, 0, 0);
858         }
859 }
860
861 void CL_ParticleRain (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int type)
862 {
863         int k;
864         float t, z, minz, maxz;
865         if (!cl_particles.integer) return;
866         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
867         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
868         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
869         if (dir[2] < 0) // falling
870         {
871                 t = (maxs[2] - mins[2]) / -dir[2];
872                 z = maxs[2];
873         }
874         else // rising??
875         {
876                 t = (maxs[2] - mins[2]) / dir[2];
877                 z = mins[2];
878         }
879         if (t < 0 || t > 2) // sanity check
880                 t = 2;
881
882         minz = z - fabs(dir[2]) * 0.1;
883         maxz = z + fabs(dir[2]) * 0.1;
884         minz = bound(mins[2], minz, maxs[2]);
885         maxz = bound(mins[2], maxz, maxs[2]);
886
887         count *= cl_particles_quality.value;
888
889         switch(type)
890         {
891         case 0:
892                 count *= 4; // ick, this should be in the mod or maps?
893
894                 while(count--)
895                 {
896                         k = particlepalette[colorbase + (rand()&3)];
897                         if (gamemode == GAME_GOODVSBAD2)
898                         {
899                                 particle(pt_rain, PARTICLE_SPARK, k, k, tex_particle, true, PBLEND_ADD, 20, 20, lhrandom(8, 16) / cl_particles_quality.value, 0, t, 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], cl.time + 9999, dir[0], dir[1], dir[2], 0, 0);
900                         }
901                         else
902                         {
903                                 particle(pt_rain, PARTICLE_SPARK, k, k, tex_particle, true, PBLEND_ADD, 0.5, 0.02, lhrandom(8, 16) / cl_particles_quality.value, 0, t, 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], cl.time + 9999, dir[0], dir[1], dir[2], 0, 0);
904                         }
905                 }
906                 break;
907         case 1:
908                 while(count--)
909                 {
910                         k = particlepalette[colorbase + (rand()&3)];
911                         if (gamemode == GAME_GOODVSBAD2)
912                         {
913                                 particle(pt_rain, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ADD, 20, 20, lhrandom(64, 128) / cl_particles_quality.value, 0, t, 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, dir[0], dir[1], dir[2], 0, 0);
914                         }
915                         else
916                         {
917                                 particle(pt_rain, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ADD, 1, 1, lhrandom(64, 128) / cl_particles_quality.value, 0, t, 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, dir[0], dir[1], dir[2], 0, 0);
918                         }
919                 }
920                 break;
921         default:
922                 Host_Error("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
923         }
924 }
925
926 void CL_Stardust (vec3_t mins, vec3_t maxs, int count)
927 {
928         int k;
929         float t;
930         vec3_t o, v, center;
931         if (!cl_particles.integer) return;
932
933         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
934         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
935         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
936
937         center[0] = (mins[0] + maxs[0]) * 0.5f;
938         center[1] = (mins[1] + maxs[1]) * 0.5f;
939         center[2] = (mins[2] + maxs[2]) * 0.5f;
940
941         count *= cl_particles_quality.value;
942         while (count--)
943         {
944                 k = particlepalette[224 + (rand()&15)];
945                 o[0] = lhrandom(mins[0], maxs[0]);
946                 o[1] = lhrandom(mins[1], maxs[1]);
947                 o[2] = lhrandom(mins[2], maxs[2]);
948                 VectorSubtract(o, center, v);
949                 VectorNormalizeFast(v);
950                 VectorScale(v, 100, v);
951                 v[2] += sv_gravity.value * 0.15f;
952                 particle(pt_static, PARTICLE_BILLBOARD, 0x903010, 0xFFD030, tex_particle, false, PBLEND_ADD, 1.5, 1.5, lhrandom(64, 128) / cl_particles_quality.value, 128 / cl_particles_quality.value, 9999, 1, 0, o[0], o[1], o[2], v[0], v[1], v[2], 0, 0, 0, 0, 0, 0);
953         }
954 }
955
956 void CL_FlameCube (vec3_t mins, vec3_t maxs, int count)
957 {
958         int k;
959         float t;
960         if (!cl_particles.integer) return;
961         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
962         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
963         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
964
965         count *= cl_particles_quality.value;
966         while (count--)
967         {
968                 k = particlepalette[224 + (rand()&15)];
969                 particle(pt_static, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ADD, 4, 4, lhrandom(64, 128) / cl_particles_quality.value, 384 / cl_particles_quality.value, 9999, -1, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), lhrandom(-32, 32), lhrandom(-32, 32), lhrandom(0, 64), 0, 0, 0, 0, 1, 0);
970                 if (count & 1)
971                         particle(pt_static, PARTICLE_BILLBOARD, 0x303030, 0x606060, tex_smoke[rand()&7], false, PBLEND_ADD, 6, 6, lhrandom(48, 96) / cl_particles_quality.value, 64 / cl_particles_quality.value, 9999, 0, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(0, 32), 0, 0, 0, 0, 0, 0);
972         }
973 }
974
975 void CL_Flames (vec3_t org, vec3_t vel, int count)
976 {
977         int k;
978         if (!cl_particles.integer) return;
979
980         count *= cl_particles_quality.value;
981         while (count--)
982         {
983                 k = particlepalette[224 + (rand()&15)];
984                 particle(pt_static, PARTICLE_BILLBOARD, k, k, tex_particle, false, PBLEND_ADD, 4, 4, lhrandom(64, 128) / cl_particles_quality.value, 384 / cl_particles_quality.value, 9999, -1, 1.1, org[0], org[1], org[2], vel[0] + lhrandom(-128, 128), vel[1] + lhrandom(-128, 128), vel[2] + lhrandom(-128, 128), 0, 0, 0, 0, 1, 0);
985         }
986 }
987
988
989
990 /*
991 ===============
992 CL_LavaSplash
993
994 ===============
995 */
996 void CL_LavaSplash (vec3_t origin)
997 {
998         float i, j, inc, vel;
999         int k, l;
1000         vec3_t          dir, org;
1001         if (!cl_particles.integer) return;
1002
1003         inc = 32 / cl_particles_quality.value;
1004         for (i = -128;i < 128;i += inc)
1005         {
1006                 for (j = -128;j < 128;j += inc)
1007                 {
1008                         dir[0] = j + lhrandom(0, 8);
1009                         dir[1] = i + lhrandom(0, 8);
1010                         dir[2] = 256;
1011                         org[0] = origin[0] + dir[0];
1012                         org[1] = origin[1] + dir[1];
1013                         org[2] = origin[2] + lhrandom(0, 64);
1014                         vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1015                         if (gamemode == GAME_GOODVSBAD2)
1016                         {
1017                                 k = particlepalette[0 + (rand()&255)];
1018                                 l = particlepalette[0 + (rand()&255)];
1019                                 particle(pt_static, PARTICLE_BILLBOARD, k, l, tex_particle, false, PBLEND_ADD, 12, 12, inc * 8, inc * 8, 9999, 0.05, 1, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, 0, 0);
1020                         }
1021                         else
1022                         {
1023                                 k = l = particlepalette[224 + (rand()&7)];
1024                                 particle(pt_static, PARTICLE_BILLBOARD, k, l, tex_particle, false, PBLEND_ADD, 12, 12, inc * 8, inc * 8, 9999, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, 0, 0);
1025                         }
1026                 }
1027         }
1028 }
1029
1030 /*
1031 ===============
1032 CL_TeleportSplash
1033
1034 ===============
1035 */
1036 #if WORKINGLQUAKE
1037 void R_TeleportSplash (vec3_t org)
1038 {
1039         float i, j, k, inc;
1040         if (!cl_particles.integer) return;
1041
1042         inc = 8 / cl_particles_quality.value;
1043         for (i = -16;i < 16;i += inc)
1044                 for (j = -16;j < 16;j += inc)
1045                         for (k = -24;k < 32;k += inc)
1046                                 particle(pt_static, PARTICLE_BILLBOARD, 0xA0A0A0, 0xFFFFFF, tex_particle, false, PBLEND_ADD, 10, 10, inc * 32, inc * lhrandom(8, 16), inc * 32, 9999, 0, 0, org[0] + i + lhrandom(0, 8), org[1] + j + lhrandom(0, 8), org[2] + k + lhrandom(0, 8), lhrandom(-64, 64), lhrandom(-64, 64), lhrandom(-256, 256), 0, 0, 0, 0, 1, 0);
1047 }
1048 #endif
1049
1050 #ifdef WORKINGLQUAKE
1051 void R_RocketTrail (vec3_t start, vec3_t end, int type)
1052 #else
1053 void CL_RocketTrail (vec3_t start, vec3_t end, int type, entity_t *ent)
1054 #endif
1055 {
1056         vec3_t vec, dir, vel, pos;
1057         float len, dec, speed, qd;
1058         int contents, smoke, blood, bubbles;
1059
1060         if (end[0] == start[0] && end[1] == start[1] && end[2] == start[2])
1061                 return;
1062
1063         VectorSubtract(end, start, dir);
1064         VectorNormalize(dir);
1065
1066         VectorSubtract (end, start, vec);
1067 #ifdef WORKINGLQUAKE
1068         len = VectorNormalize (vec);
1069         dec = 0;
1070         speed = 1.0f / cl.frametime;
1071         VectorSubtract(end, start, vel);
1072 #else
1073         len = VectorNormalizeLength (vec);
1074         dec = -ent->persistent.trail_time;
1075         ent->persistent.trail_time += len;
1076         if (ent->persistent.trail_time < 0.01f)
1077                 return;
1078
1079         // if we skip out, leave it reset
1080         ent->persistent.trail_time = 0.0f;
1081
1082         speed = 1.0f / (ent->state_current.time - ent->state_previous.time);
1083         VectorSubtract(ent->state_current.origin, ent->state_previous.origin, vel);
1084 #endif
1085         VectorScale(vel, speed, vel);
1086
1087         // advance into this frame to reach the first puff location
1088         VectorMA(start, dec, vec, pos);
1089         len -= dec;
1090
1091         contents = CL_PointQ1Contents(pos);
1092         if (contents == CONTENTS_SKY || contents == CONTENTS_LAVA)
1093                 return;
1094
1095         smoke = cl_particles.integer && cl_particles_smoke.integer;
1096         blood = cl_particles.integer && cl_particles_blood.integer;
1097         bubbles = cl_particles.integer && cl_particles_bubbles.integer && (contents == CONTENTS_WATER || contents == CONTENTS_SLIME);
1098         qd = 1.0f / cl_particles_quality.value;
1099
1100         while (len >= 0)
1101         {
1102                 switch (type)
1103                 {
1104                         case 0: // rocket trail
1105                                 dec = qd*3;
1106                                 if (smoke)
1107                                 {
1108                                         particle(pt_grow,   PARTICLE_BILLBOARD, 0x303030, 0x606060, tex_smoke[rand()&7], false, PBLEND_ADD, 3, 3, qd*cl_particles_smoke_alpha.value*125, qd*cl_particles_smoke_alphafade.value*125, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-5, 5), lhrandom(-5, 5), lhrandom(-5, 5), 7, 0, 0, 0, 0, 0);
1109                                         particle(pt_static, PARTICLE_BILLBOARD, 0x801010, 0xFFA020, tex_smoke[rand()&7], false, PBLEND_ADD, 3, 3, qd*cl_particles_smoke_alpha.value*288, qd*cl_particles_smoke_alphafade.value*1400, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-20, 20), lhrandom(-20, 20), lhrandom(-20, 20), 0, 0, 0, 0, 0, 0);
1110                                 }
1111                                 if (bubbles)
1112                                         particle(pt_bubble, PARTICLE_BILLBOARD, 0x404040, 0x808080, tex_bubble, false, PBLEND_ADD, 2, 2, qd*lhrandom(64, 255), qd*256, 9999, -0.25, 1.5, pos[0], pos[1], pos[2], lhrandom(-16, 16), lhrandom(-16, 16), lhrandom(-16, 16), 0, 0, 0, 0, (1.0 / 16.0), 0);
1113                                 break;
1114
1115                         case 1: // grenade trail
1116                                 // FIXME: make it gradually stop smoking
1117                                 dec = qd*3;
1118                                 if (smoke)
1119                                         particle(pt_grow, PARTICLE_BILLBOARD, 0x303030, 0x606060, tex_smoke[rand()&7], false, PBLEND_ADD, 3, 3, qd*cl_particles_smoke_alpha.value*100, qd*cl_particles_smoke_alphafade.value*100, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-5, 5), lhrandom(-5, 5), lhrandom(-5, 5), 7, 0, 0, 0, 0, 0);
1120                                 break;
1121
1122
1123                         case 2: // blood
1124                         case 4: // slight blood
1125                                 dec = qd*16;
1126                                 if (blood)
1127                                         particle(pt_blood, PARTICLE_BILLBOARD, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], true, PBLEND_MOD, 8, 8, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 9999, 0, -1, pos[0], pos[1], pos[2], vel[0] * 0.5f + lhrandom(-64, 64), vel[1] * 0.5f + lhrandom(-64, 64), vel[2] * 0.5f + lhrandom(-64, 64), 0, 0, 0, 0, 1, 0);
1128                                 break;
1129
1130                         case 3: // green tracer
1131                                 dec = qd*6;
1132                                 if (smoke)
1133                                 {
1134                                         if (gamemode == GAME_GOODVSBAD2)
1135                                                 particle(pt_static, PARTICLE_BILLBOARD, 0x00002E, 0x000030, tex_particle, false, PBLEND_ADD, 6, 6, qd*128, qd*384, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0, 0, 0, 0, 0, 0);
1136                                         else
1137                                                 particle(pt_static, PARTICLE_BILLBOARD, 0x002000, 0x003000, tex_particle, false, PBLEND_ADD, 6, 6, qd*128, qd*384, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0, 0, 0, 0, 0, 0);
1138                                 }
1139                                 break;
1140
1141                         case 5: // flame tracer
1142                                 dec = qd*6;
1143                                 if (smoke)
1144                                         particle(pt_static, PARTICLE_BILLBOARD, 0x301000, 0x502000, tex_particle, false, PBLEND_ADD, 6, 6, qd*128, qd*384, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0, 0, 0, 0, 0, 0);
1145                                 break;
1146
1147                         case 6: // voor trail
1148                                 dec = qd*6;
1149                                 if (smoke)
1150                                 {
1151                                         if (gamemode == GAME_GOODVSBAD2)
1152                                                 particle(pt_static, PARTICLE_BILLBOARD, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, false, PBLEND_ALPHA, 6, 6, qd*255, qd*384, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0, 0, 0, 0, 0, 0);
1153                                         else
1154                                                 particle(pt_static, PARTICLE_BILLBOARD, 0x502030, 0x502030, tex_particle, false, PBLEND_ADD, 6, 6, qd*128, qd*384, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-8, 8), lhrandom(-8, 8), lhrandom(-8, 8), 0, 0, 0, 0, 0, 0);
1155                                 }
1156                                 break;
1157
1158                         case 7: // Nehahra smoke tracer
1159                                 dec = qd*7;
1160                                 if (smoke)
1161                                         particle(pt_static, PARTICLE_BILLBOARD, 0x303030, 0x606060, tex_smoke[rand()&7], true, PBLEND_ALPHA, 7, 7, qd*64, qd*320, 9999, 0, 0, pos[0], pos[1], pos[2], lhrandom(-4, 4), lhrandom(-4, 4), lhrandom(0, 16), 0, 0, 0, 0, 0, 0);
1162                                 break;
1163                         case 8: // Nexuiz plasma trail
1164                                 dec = qd*4;
1165                                 if (smoke)
1166                                         particle(pt_static, PARTICLE_BILLBOARD, 0x283880, 0x283880, tex_particle, false, PBLEND_ADD, 4, 4, qd*255, qd*1024, 9999, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, 0, 0);
1167                                 break;
1168                 }
1169
1170                 // advance to next time and position
1171                 len -= dec;
1172                 VectorMA (pos, dec, vec, pos);
1173         }
1174 #ifndef WORKINGLQUAKE
1175         ent->persistent.trail_time = len;
1176 #endif
1177 }
1178
1179 void CL_RocketTrail2 (vec3_t start, vec3_t end, int color, entity_t *ent)
1180 {
1181         float dec, len;
1182         vec3_t vec, pos;
1183         if (!cl_particles.integer) return;
1184         if (!cl_particles_smoke.integer) return;
1185
1186         VectorCopy(start, pos);
1187         VectorSubtract(end, start, vec);
1188 #ifdef WORKINGLQUAKE
1189         len = VectorNormalize(vec);
1190 #else
1191         len = VectorNormalizeLength(vec);
1192 #endif
1193         color = particlepalette[color];
1194         dec = 3.0f / cl_particles_quality.value;
1195         while (len > 0)
1196         {
1197                 particle(pt_static, PARTICLE_BILLBOARD, color, color, tex_particle, false, PBLEND_ALPHA, 5, 5, 128 / cl_particles_quality.value, 320 / cl_particles_quality.value, 9999, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, 0, 0);
1198                 len -= dec;
1199                 VectorMA(pos, dec, vec, pos);
1200         }
1201 }
1202
1203 void CL_BeamParticle (const vec3_t start, const vec3_t end, vec_t radius, float red, float green, float blue, float alpha, float lifetime)
1204 {
1205         int tempcolor2, cr, cg, cb;
1206         cr = red * 255;
1207         cg = green * 255;
1208         cb = blue * 255;
1209         tempcolor2 = (bound(0, cr, 255) << 16) | (bound(0, cg, 255) << 8) | bound(0, cb, 255);
1210         particle(pt_static, PARTICLE_BEAM, tempcolor2, tempcolor2, tex_beam, false, PBLEND_ADD, radius, radius, alpha * 255, alpha * 255 / lifetime, 9999, 0, 0, start[0], start[1], start[2], 0, 0, 0, 0, end[0], end[1], end[2], 0, 0);
1211 }
1212
1213 void CL_Tei_Smoke(const vec3_t org, const vec3_t dir, int count)
1214 {
1215         float f;
1216         if (!cl_particles.integer) return;
1217
1218         // smoke puff
1219         if (cl_particles_smoke.integer)
1220                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1221                         particle(pt_grow, PARTICLE_BILLBOARD, 0x202020, 0x404040, tex_smoke[rand()&7], true, PBLEND_ADD, 5, 5, 255 / cl_particles_quality.value, 512 / cl_particles_quality.value, 9999, 0, 0, org[0] + 0.125f * lhrandom(-count, count), org[1] + 0.125f * lhrandom (-count, count), org[2] + 0.125f * lhrandom(-count, count), dir[0] + lhrandom(-count, count) * 0.5f, dir[1] + lhrandom(-count, count) * 0.5f, dir[2] + lhrandom(-count, count) * 0.5f, 15, 0, 0, 0, 0, 0);
1222 }
1223
1224 void CL_Tei_PlasmaHit(const vec3_t org, const vec3_t dir, int count)
1225 {
1226         float f;
1227         if (!cl_particles.integer) return;
1228
1229         if (cl_stainmaps.integer)
1230                 R_Stain(org, 40, 96, 96, 96, 40, 128, 128, 128, 40);
1231         CL_SpawnDecalParticleForPoint(org, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1232
1233         // smoke puff
1234         if (cl_particles_smoke.integer)
1235                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1236                         particle(pt_grow, PARTICLE_BILLBOARD, 0x202020, 0x404040, tex_smoke[rand()&7], true, PBLEND_ADD, 5, 5, 255 / cl_particles_quality.value, 512 / cl_particles_quality.value, 9999, 0, 0, org[0] + 0.125f * lhrandom(-count, count), org[1] + 0.125f * lhrandom (-count, count), org[2] + 0.125f * lhrandom(-count, count), dir[0] + lhrandom(-count, count), dir[1] + lhrandom(-count, count), dir[2] + lhrandom(-count, count), 15, 0, 0, 0, 0, 0);
1237
1238         // sparks
1239         if (cl_particles_sparks.integer)
1240                 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1241                         particle(pt_static, PARTICLE_SPARK, 0x2030FF, 0x80C0FF, tex_particle, false, PBLEND_ADD, 2.0f, 0.1f, lhrandom(64, 255) / cl_particles_quality.value, 512 / cl_particles_quality.value, 9999, 0, 0, org[0], org[1], org[2], lhrandom(-count, count) * 3.0f + dir[0], lhrandom(-count, count) * 3.0f + dir[1], lhrandom(-count, count) * 3.0f + dir[2], 0, 0, 0, 0, 0, 0);
1242 }
1243
1244 /*
1245 ===============
1246 CL_MoveParticles
1247 ===============
1248 */
1249 void CL_MoveParticles (void)
1250 {
1251         particle_t *p;
1252         int i, activeparticles, maxparticle, j, a, pressureused = false, content;
1253         float gravity, dvel, bloodwaterfade, frametime, f, dist, normal[3], v[3], org[3];
1254 #ifdef WORKINGLQUAKE
1255         void *hitent;
1256 #else
1257         entity_render_t *hitent;
1258 #endif
1259
1260         // LordHavoc: early out condition
1261         if (!cl_numparticles)
1262                 return;
1263
1264 #ifdef WORKINGLQUAKE
1265         frametime = cl.frametime;
1266 #else
1267         frametime = cl.time - cl.oldtime;
1268 #endif
1269         gravity = frametime * sv_gravity.value;
1270         dvel = 1+4*frametime;
1271         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1272
1273         activeparticles = 0;
1274         maxparticle = -1;
1275         j = 0;
1276         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1277         {
1278                 content = 0;
1279                 VectorCopy(p->org, p->oldorg);
1280                 VectorMA(p->org, frametime, p->vel, p->org);
1281                 VectorCopy(p->org, org);
1282                 if (p->bounce)
1283                 {
1284                         if (CL_TraceLine(p->oldorg, p->org, v, normal, true, &hitent, SUPERCONTENTS_SOLID) < 1)
1285                         {
1286                                 VectorCopy(v, p->org);
1287                                 if (p->bounce < 0)
1288                                 {
1289                                         // assume it's blood (lame, but...)
1290 #ifndef WORKINGLQUAKE
1291                                         if (cl_stainmaps.integer)
1292                                                 R_Stain(v, 32, 32, 16, 16, p->alpha * p->scalex * (1.0f / 40.0f), 192, 48, 48, p->alpha * p->scalex * (1.0f / 40.0f));
1293 #endif
1294                                         if (cl_decals.integer)
1295                                         {
1296                                                 p->type = pt_decal;
1297                                                 p->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
1298                                                 // convert from a blood particle to a blood decal
1299                                                 p->texnum = tex_blooddecal[rand()&7];
1300 #ifndef WORKINGLQUAKE
1301                                                 p->owner = hitent;
1302                                                 p->ownermodel = hitent->model;
1303                                                 Matrix4x4_Transform(&hitent->inversematrix, v, p->relativeorigin);
1304                                                 Matrix4x4_Transform3x3(&hitent->inversematrix, normal, p->relativedirection);
1305                                                 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
1306 #endif
1307                                                 p->time2 = cl.time + cl_decals_time.value;
1308                                                 p->die = p->time2 + cl_decals_fadetime.value;
1309                                                 p->alphafade = 0;
1310                                                 VectorCopy(normal, p->vel2);
1311                                                 VectorClear(p->vel);
1312                                                 VectorAdd(p->org, normal, p->org);
1313                                                 p->bounce = 0;
1314                                                 p->friction = 0;
1315                                                 p->gravity = 0;
1316                                                 p->scalex *= 1.25f;
1317                                                 p->scaley *= 1.25f;
1318                                         }
1319                                         else
1320                                         {
1321                                                 p->die = -1;
1322                                                 freeparticles[j++] = p;
1323                                                 continue;
1324                                         }
1325                                 }
1326                                 else
1327                                 {
1328                                         dist = DotProduct(p->vel, normal) * -p->bounce;
1329                                         VectorMA(p->vel, dist, normal, p->vel);
1330                                         if (DotProduct(p->vel, p->vel) < 0.03)
1331                                                 VectorClear(p->vel);
1332                                 }
1333                         }
1334                 }
1335                 p->vel[2] -= p->gravity * gravity;
1336                 p->alpha -= p->alphafade * frametime;
1337                 if (p->friction)
1338                 {
1339                         f = p->friction * frametime;
1340                         if (!content)
1341                                 content = CL_PointQ1Contents(p->org);
1342                         if (content != CONTENTS_EMPTY)
1343                                 f *= 4;
1344                         f = 1.0f - f;
1345                         VectorScale(p->vel, f, p->vel);
1346                 }
1347
1348                 if (p->type != pt_static)
1349                 {
1350                         switch (p->type)
1351                         {
1352                         case pt_blood:
1353                                 if (!content)
1354                                         content = CL_PointQ1Contents(p->org);
1355                                 a = content;
1356                                 if (a != CONTENTS_EMPTY)
1357                                 {
1358                                         if (a == CONTENTS_WATER || a == CONTENTS_SLIME)
1359                                         {
1360                                                 p->scalex += frametime * 8;
1361                                                 p->scaley += frametime * 8;
1362                                                 //p->alpha -= bloodwaterfade;
1363                                         }
1364                                         else
1365                                                 p->die = -1;
1366                                 }
1367                                 else
1368                                         p->vel[2] -= gravity;
1369                                 break;
1370                         case pt_bubble:
1371                                 if (!content)
1372                                         content = CL_PointQ1Contents(p->org);
1373                                 if (content != CONTENTS_WATER && content != CONTENTS_SLIME)
1374                                 {
1375                                         p->die = -1;
1376                                         break;
1377                                 }
1378                                 break;
1379                         case pt_rain:
1380                                 if (cl.time > p->time2)
1381                                 {
1382                                         // snow flutter
1383                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1384                                         p->vel[0] = lhrandom(-32, 32) + p->vel2[0];
1385                                         p->vel[1] = lhrandom(-32, 32) + p->vel2[1];
1386                                         p->vel[2] = /*lhrandom(-32, 32) +*/ p->vel2[2];
1387                                 }
1388                                 if (!content)
1389                                         content = CL_PointQ1Contents(p->org);
1390                                 a = content;
1391                                 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1392                                         p->die = -1;
1393                                 break;
1394                         case pt_grow:
1395                                 p->scalex += frametime * p->time2;
1396                                 p->scaley += frametime * p->time2;
1397                                 break;
1398                         case pt_decal:
1399 #ifndef WORKINGLQUAKE
1400                                 if (p->owner->model == p->ownermodel)
1401                                 {
1402                                         Matrix4x4_Transform(&p->owner->matrix, p->relativeorigin, p->org);
1403                                         Matrix4x4_Transform3x3(&p->owner->matrix, p->relativedirection, p->vel2);
1404                                 }
1405                                 else
1406                                         p->die = -1;
1407 #endif
1408                                 if (cl.time > p->time2)
1409                                 {
1410                                         p->alphafade = p->alpha / (p->die - cl.time);
1411                                         p->type = pt_decalfade;
1412                                 }
1413                                 break;
1414                         case pt_decalfade:
1415 #ifndef WORKINGLQUAKE
1416                                 if (p->owner->model == p->ownermodel)
1417                                 {
1418                                         Matrix4x4_Transform(&p->owner->matrix, p->relativeorigin, p->org);
1419                                         Matrix4x4_Transform3x3(&p->owner->matrix, p->relativedirection, p->vel2);
1420                                 }
1421                                 else
1422                                         p->die = -1;
1423 #endif
1424                                 break;
1425                         default:
1426                                 Con_Printf("unknown particle type %i\n", p->type);
1427                                 p->die = -1;
1428                                 break;
1429                         }
1430                 }
1431
1432                 // remove dead particles
1433                 if (p->alpha < 1 || p->die < cl.time)
1434                         freeparticles[j++] = p;
1435                 else
1436                 {
1437                         maxparticle = i;
1438                         activeparticles++;
1439                         if (p->pressure)
1440                                 pressureused = true;
1441                 }
1442         }
1443         // fill in gaps to compact the array
1444         i = 0;
1445         while (maxparticle >= activeparticles)
1446         {
1447                 *freeparticles[i++] = particles[maxparticle--];
1448                 while (maxparticle >= activeparticles && particles[maxparticle].die < cl.time)
1449                         maxparticle--;
1450         }
1451         cl_numparticles = activeparticles;
1452
1453         if (pressureused)
1454         {
1455                 activeparticles = 0;
1456                 for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1457                         if (p->pressure)
1458                                 freeparticles[activeparticles++] = p;
1459
1460                 if (activeparticles)
1461                 {
1462                         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1463                         {
1464                                 for (j = 0;j < activeparticles;j++)
1465                                 {
1466                                         if (freeparticles[j] != p)
1467                                         {
1468                                                 float dist, diff[3];
1469                                                 VectorSubtract(p->org, freeparticles[j]->org, diff);
1470                                                 dist = DotProduct(diff, diff);
1471                                                 if (dist < 4096 && dist >= 1)
1472                                                 {
1473                                                         dist = freeparticles[j]->scalex * 4.0f * frametime / sqrt(dist);
1474                                                         VectorMA(p->vel, dist, diff, p->vel);
1475                                                 }
1476                                         }
1477                                 }
1478                         }
1479                 }
1480         }
1481 }
1482
1483 #define MAX_PARTICLETEXTURES 64
1484 // particletexture_t is a rectangle in the particlefonttexture
1485 typedef struct
1486 {
1487         rtexture_t *texture;
1488         float s1, t1, s2, t2;
1489 }
1490 particletexture_t;
1491
1492 #if WORKINGLQUAKE
1493 static int particlefonttexture;
1494 #else
1495 static rtexturepool_t *particletexturepool;
1496 static rtexture_t *particlefonttexture;
1497 #endif
1498 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1499
1500 static cvar_t r_drawparticles = {0, "r_drawparticles", "1"};
1501
1502 static qbyte shadebubble(float dx, float dy, vec3_t light)
1503 {
1504         float dz, f, dot;
1505         vec3_t normal;
1506         dz = 1 - (dx*dx+dy*dy);
1507         if (dz > 0) // it does hit the sphere
1508         {
1509                 f = 0;
1510                 // back side
1511                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1512                 VectorNormalize(normal);
1513                 dot = DotProduct(normal, light);
1514                 if (dot > 0.5) // interior reflection
1515                         f += ((dot *  2) - 1);
1516                 else if (dot < -0.5) // exterior reflection
1517                         f += ((dot * -2) - 1);
1518                 // front side
1519                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1520                 VectorNormalize(normal);
1521                 dot = DotProduct(normal, light);
1522                 if (dot > 0.5) // interior reflection
1523                         f += ((dot *  2) - 1);
1524                 else if (dot < -0.5) // exterior reflection
1525                         f += ((dot * -2) - 1);
1526                 f *= 128;
1527                 f += 16; // just to give it a haze so you can see the outline
1528                 f = bound(0, f, 255);
1529                 return (qbyte) f;
1530         }
1531         else
1532                 return 0;
1533 }
1534
1535 static void setuptex(int texnum, qbyte *data, qbyte *particletexturedata)
1536 {
1537         int basex, basey, y;
1538         basex = ((texnum >> 0) & 7) * 32;
1539         basey = ((texnum >> 3) & 7) * 32;
1540         particletexture[texnum].s1 = (basex + 1) / 256.0f;
1541         particletexture[texnum].t1 = (basey + 1) / 256.0f;
1542         particletexture[texnum].s2 = (basex + 31) / 256.0f;
1543         particletexture[texnum].t2 = (basey + 31) / 256.0f;
1544         for (y = 0;y < 32;y++)
1545                 memcpy(particletexturedata + ((basey + y) * 256 + basex) * 4, data + y * 32 * 4, 32 * 4);
1546 }
1547
1548 void particletextureblotch(qbyte *data, float radius, float red, float green, float blue, float alpha)
1549 {
1550         int x, y;
1551         float cx, cy, dx, dy, f, iradius;
1552         qbyte *d;
1553         cx = lhrandom(radius + 1, 30 - radius);
1554         cy = lhrandom(radius + 1, 30 - radius);
1555         iradius = 1.0f / radius;
1556         alpha *= (1.0f / 255.0f);
1557         for (y = 0;y < 32;y++)
1558         {
1559                 for (x = 0;x < 32;x++)
1560                 {
1561                         dx = (x - cx);
1562                         dy = (y - cy);
1563                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1564                         if (f > 0)
1565                         {
1566                                 d = data + (y * 32 + x) * 4;
1567                                 d[0] += f * (red   - d[0]);
1568                                 d[1] += f * (green - d[1]);
1569                                 d[2] += f * (blue  - d[2]);
1570                         }
1571                 }
1572         }
1573 }
1574
1575 void particletextureclamp(qbyte *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1576 {
1577         int i;
1578         for (i = 0;i < 32*32;i++, data += 4)
1579         {
1580                 data[0] = bound(minr, data[0], maxr);
1581                 data[1] = bound(ming, data[1], maxg);
1582                 data[2] = bound(minb, data[2], maxb);
1583         }
1584 }
1585
1586 void particletextureinvert(qbyte *data)
1587 {
1588         int i;
1589         for (i = 0;i < 32*32;i++, data += 4)
1590         {
1591                 data[0] = 255 - data[0];
1592                 data[1] = 255 - data[1];
1593                 data[2] = 255 - data[2];
1594         }
1595 }
1596
1597 static void R_InitParticleTexture (void)
1598 {
1599         int x, y, d, i, j, k, m;
1600         float dx, dy, radius, f, f2;
1601         qbyte data[32][32][4], noise1[64][64], noise2[64][64], data2[64][16][4];
1602         vec3_t light;
1603         qbyte particletexturedata[256*256*4];
1604
1605         // a note: decals need to modulate (multiply) the background color to
1606         // properly darken it (stain), and they need to be able to alpha fade,
1607         // this is a very difficult challenge because it means fading to white
1608         // (no change to background) rather than black (darkening everything
1609         // behind the whole decal polygon), and to accomplish this the texture is
1610         // inverted (dark red blood on white background becomes brilliant cyan
1611         // and white on black background) so we can alpha fade it to black, then
1612         // we invert it again during the blendfunc to make it work...
1613
1614         memset(particletexturedata, 255, sizeof(particletexturedata));
1615
1616         // smoke
1617         for (i = 0;i < 8;i++)
1618         {
1619                 memset(&data[0][0][0], 255, sizeof(data));
1620                 do
1621                 {
1622                         fractalnoise(&noise1[0][0], 64, 4);
1623                         fractalnoise(&noise2[0][0], 64, 8);
1624                         m = 0;
1625                         for (y = 0;y < 32;y++)
1626                         {
1627                                 dy = y - 16;
1628                                 for (x = 0;x < 32;x++)
1629                                 {
1630                                         dx = x - 16;
1631                                         d = (noise2[y][x] - 128) * 3 + 192;
1632                                         if (d > 0)
1633                                                 d = d * (256 - (int) (dx*dx+dy*dy)) / 256;
1634                                         d = (d * noise1[y][x]) >> 7;
1635                                         d = bound(0, d, 255);
1636                                         data[y][x][3] = (qbyte) d;
1637                                         if (m < d)
1638                                                 m = d;
1639                                 }
1640                         }
1641                 }
1642                 while (m < 224);
1643                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1644         }
1645
1646         // rain splash
1647         for (i = 0;i < 16;i++)
1648         {
1649                 memset(&data[0][0][0], 255, sizeof(data));
1650                 radius = i * 3.0f / 16.0f;
1651                 f2 = 255.0f * ((15.0f - i) / 15.0f);
1652                 for (y = 0;y < 32;y++)
1653                 {
1654                         dy = (y - 16) * 0.25f;
1655                         for (x = 0;x < 32;x++)
1656                         {
1657                                 dx = (x - 16) * 0.25f;
1658                                 f = (1.0 - fabs(radius - sqrt(dx*dx+dy*dy))) * f2;
1659                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1660                         }
1661                 }
1662                 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1663         }
1664
1665         // normal particle
1666         memset(&data[0][0][0], 255, sizeof(data));
1667         for (y = 0;y < 32;y++)
1668         {
1669                 dy = y - 16;
1670                 for (x = 0;x < 32;x++)
1671                 {
1672                         dx = x - 16;
1673                         d = (256 - (dx*dx+dy*dy));
1674                         d = bound(0, d, 255);
1675                         data[y][x][3] = (qbyte) d;
1676                 }
1677         }
1678         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1679
1680         // rain
1681         memset(&data[0][0][0], 255, sizeof(data));
1682         light[0] = 1;light[1] = 1;light[2] = 1;
1683         VectorNormalize(light);
1684         for (y = 0;y < 32;y++)
1685                 for (x = 0;x < 32;x++)
1686                         data[y][x][3] = shadebubble((x - 16) * (1.0 / 8.0), y < 24 ? (y - 24) * (1.0 / 24.0) : (y - 24) * (1.0 / 8.0), light);
1687         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1688
1689         // bubble
1690         memset(&data[0][0][0], 255, sizeof(data));
1691         light[0] = 1;light[1] = 1;light[2] = 1;
1692         VectorNormalize(light);
1693         for (y = 0;y < 32;y++)
1694                 for (x = 0;x < 32;x++)
1695                         data[y][x][3] = shadebubble((x - 16) * (1.0 / 16.0), (y - 16) * (1.0 / 16.0), light);
1696         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1697
1698         // blood particles
1699         for (i = 0;i < 8;i++)
1700         {
1701                 memset(&data[0][0][0], 255, sizeof(data));
1702                 for (k = 0;k < 24;k++)
1703                         particletextureblotch(&data[0][0][0], 2, 96, 0, 0, 160);
1704                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1705                 particletextureinvert(&data[0][0][0]);
1706                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1707         }
1708
1709         // blood decals
1710         for (i = 0;i < 8;i++)
1711         {
1712                 memset(&data[0][0][0], 255, sizeof(data));
1713                 for (k = 0;k < 24;k++)
1714                         particletextureblotch(&data[0][0][0], 2, 96, 0, 0, 96);
1715                 for (j = 3;j < 7;j++)
1716                         for (k = 0, m = rand() % 12;k < m;k++)
1717                                 particletextureblotch(&data[0][0][0], j, 96, 0, 0, 192);
1718                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1719                 particletextureinvert(&data[0][0][0]);
1720                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1721         }
1722
1723         // bullet decals
1724         for (i = 0;i < 8;i++)
1725         {
1726                 memset(&data[0][0][0], 255, sizeof(data));
1727                 for (k = 0;k < 12;k++)
1728                         particletextureblotch(&data[0][0][0], 2, 0, 0, 0, 128);
1729                 for (k = 0;k < 3;k++)
1730                         particletextureblotch(&data[0][0][0], 14, 0, 0, 0, 160);
1731                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1732                 particletextureinvert(&data[0][0][0]);
1733                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1734         }
1735
1736 #if WORKINGLQUAKE
1737         glBindTexture(GL_TEXTURE_2D, (particlefonttexture = gl_extension_number++));
1738         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1739         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1740 #else
1741         particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", 256, 256, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1742         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1743                 particletexture[i].texture = particlefonttexture;
1744
1745         // beam
1746         fractalnoise(&noise1[0][0], 64, 4);
1747         m = 0;
1748         for (y = 0;y < 64;y++)
1749         {
1750                 for (x = 0;x < 16;x++)
1751                 {
1752                         if (x < 8)
1753                                 d = x;
1754                         else
1755                                 d = (15 - x);
1756                         d = d * d * noise1[y][x] / (7 * 7);
1757                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (qbyte) bound(0, d, 255);
1758                         data2[y][x][3] = 255;
1759                 }
1760         }
1761
1762         particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "beam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1763         particletexture[tex_beam].s1 = 0;
1764         particletexture[tex_beam].t1 = 0;
1765         particletexture[tex_beam].s2 = 1;
1766         particletexture[tex_beam].t2 = 1;
1767 #endif
1768 }
1769
1770 static void r_part_start(void)
1771 {
1772         particletexturepool = R_AllocTexturePool();
1773         R_InitParticleTexture ();
1774 }
1775
1776 static void r_part_shutdown(void)
1777 {
1778         R_FreeTexturePool(&particletexturepool);
1779 }
1780
1781 static void r_part_newmap(void)
1782 {
1783         cl_numparticles = 0;
1784 }
1785
1786 void R_Particles_Init (void)
1787 {
1788         Cvar_RegisterVariable(&r_drawparticles);
1789 #ifdef WORKINGLQUAKE
1790         r_part_start();
1791 #else
1792         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1793 #endif
1794 }
1795
1796 #ifdef WORKINGLQUAKE
1797 void R_InitParticles(void)
1798 {
1799         CL_Particles_Init();
1800         R_Particles_Init();
1801 }
1802 #endif
1803
1804 float particle_vertex3f[12], particle_texcoord2f[8];
1805
1806 #ifdef WORKINGLQUAKE
1807 void R_DrawParticle(particle_t *p)
1808 {
1809 #else
1810 void R_DrawParticleCallback(const void *calldata1, int calldata2)
1811 {
1812         const particle_t *p = calldata1;
1813         rmeshstate_t m;
1814 #endif
1815         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, fogvec[3], cr, cg, cb, ca;
1816         particletexture_t *tex;
1817
1818         VectorCopy(p->org, org);
1819
1820         tex = &particletexture[p->texnum];
1821         cr = p->color[0] * (1.0f / 255.0f);
1822         cg = p->color[1] * (1.0f / 255.0f);
1823         cb = p->color[2] * (1.0f / 255.0f);
1824         ca = p->alpha * (1.0f / 255.0f);
1825         if (p->blendmode == PBLEND_MOD)
1826         {
1827                 cr *= ca;
1828                 cg *= ca;
1829                 cb *= ca;
1830                 cr = min(cr, 1);
1831                 cg = min(cg, 1);
1832                 cb = min(cb, 1);
1833                 ca = 1;
1834         }
1835
1836 #ifndef WORKINGLQUAKE
1837         if (fogenabled && p->blendmode != PBLEND_MOD)
1838         {
1839                 VectorSubtract(org, r_vieworigin, fogvec);
1840                 fog = exp(fogdensity/DotProduct(fogvec,fogvec));
1841                 ifog = 1 - fog;
1842                 cr = cr * ifog;
1843                 cg = cg * ifog;
1844                 cb = cb * ifog;
1845                 if (p->blendmode == 0)
1846                 {
1847                         cr += fogcolor[0] * fog;
1848                         cg += fogcolor[1] * fog;
1849                         cb += fogcolor[2] * fog;
1850                 }
1851         }
1852
1853         GL_Color(cr, cg, cb, ca);
1854
1855         R_Mesh_Matrix(&r_identitymatrix);
1856
1857         memset(&m, 0, sizeof(m));
1858         m.tex[0] = R_GetTexture(tex->texture);
1859         m.pointer_texcoord[0] = particle_texcoord2f;
1860         R_Mesh_State_Texture(&m);
1861
1862         if (p->blendmode == 0)
1863                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1864         else if (p->blendmode == 1)
1865                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
1866         else
1867                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1868         GL_DepthMask(false);
1869         GL_DepthTest(true);
1870         GL_VertexPointer(particle_vertex3f);
1871 #endif
1872         if (p->orientation == PARTICLE_BILLBOARD || p->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1873         {
1874                 if (p->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1875                 {
1876                         // double-sided
1877                         if (DotProduct(p->vel2, r_vieworigin) > DotProduct(p->vel2, org))
1878                         {
1879                                 VectorNegate(p->vel2, v);
1880                                 VectorVectors(v, right, up);
1881                         }
1882                         else
1883                                 VectorVectors(p->vel2, right, up);
1884                         VectorScale(right, p->scalex, right);
1885                         VectorScale(up, p->scaley, up);
1886                 }
1887                 else
1888                 {
1889                         VectorScale(r_viewleft, -p->scalex, right);
1890                         VectorScale(r_viewup, p->scaley, up);
1891                 }
1892                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
1893                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
1894                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
1895                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
1896                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
1897                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
1898                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
1899                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
1900                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
1901                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
1902                 particle_vertex3f[10] = org[1] + right[1] - up[1];
1903                 particle_vertex3f[11] = org[2] + right[2] - up[2];
1904                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
1905                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
1906                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
1907                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
1908         }
1909         else if (p->orientation == PARTICLE_SPARK)
1910         {
1911                 VectorMA(p->org, -p->scaley, p->vel, v);
1912                 VectorMA(p->org, p->scaley, p->vel, up2);
1913                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, p->scalex);
1914                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
1915                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
1916                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
1917                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
1918         }
1919         else if (p->orientation == PARTICLE_BEAM)
1920         {
1921                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel2, p->scalex);
1922                 VectorSubtract(p->vel2, p->org, up);
1923                 VectorNormalizeFast(up);
1924                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) - cl.time * 0.25;
1925                 v[1] = DotProduct(p->vel2, up) * (1.0f / 64.0f) - cl.time * 0.25;
1926                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
1927                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
1928                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
1929                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
1930         }
1931         else
1932                 Host_Error("R_DrawParticles: unknown particle orientation %i\n", p->orientation);
1933
1934 #if WORKINGLQUAKE
1935         if (p->blendmode == 0)
1936                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1937         else if (p->blendmode == 1)
1938                 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1939         else
1940                 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1941         glColor4f(cr, cg, cb, ca);
1942         glBegin(GL_QUADS);
1943         glTexCoord2f(particle_texcoord2f[0], particle_texcoord2f[1]);glVertex3f(particle_vertex3f[ 0], particle_vertex3f[ 1], particle_vertex3f[ 2]);
1944         glTexCoord2f(particle_texcoord2f[2], particle_texcoord2f[3]);glVertex3f(particle_vertex3f[ 3], particle_vertex3f[ 4], particle_vertex3f[ 5]);
1945         glTexCoord2f(particle_texcoord2f[4], particle_texcoord2f[5]);glVertex3f(particle_vertex3f[ 6], particle_vertex3f[ 7], particle_vertex3f[ 8]);
1946         glTexCoord2f(particle_texcoord2f[6], particle_texcoord2f[7]);glVertex3f(particle_vertex3f[ 9], particle_vertex3f[10], particle_vertex3f[11]);
1947         glEnd();
1948 #else
1949         R_Mesh_Draw(4, 2, polygonelements);
1950 #endif
1951 }
1952
1953 void R_DrawParticles (void)
1954 {
1955         int i;
1956         float minparticledist;
1957         particle_t *p;
1958
1959 #ifdef WORKINGLQUAKE
1960         CL_MoveParticles();
1961 #endif
1962
1963         // LordHavoc: early out conditions
1964         if ((!cl_numparticles) || (!r_drawparticles.integer))
1965                 return;
1966
1967         minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
1968
1969 #ifdef WORKINGLQUAKE
1970         glBindTexture(GL_TEXTURE_2D, particlefonttexture);
1971         glEnable(GL_BLEND);
1972         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1973         glDepthMask(0);
1974         // LordHavoc: only render if not too close
1975         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1976                 if (DotProduct(p->org, r_viewforward) >= minparticledist)
1977                         R_DrawParticle(p);
1978         glDepthMask(1);
1979         glDisable(GL_BLEND);
1980         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1981 #else
1982         // LordHavoc: only render if not too close
1983         c_particles += cl_numparticles;
1984         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1985                 if (DotProduct(p->org, r_viewforward) >= minparticledist || p->orientation == PARTICLE_BEAM)
1986                         R_MeshQueue_AddTransparent(p->org, R_DrawParticleCallback, p, 0);
1987 #endif
1988 }
1989