redesigned blood effects (now looks like droplets until they hit a surface)
[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_PointContents(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_origin, org1, diff);
53         VectorNormalizeFast (diff);
54         CrossProduct (normal, diff, right1);
55
56         // calculate 'right' vector for end
57         VectorSubtract (r_origin, 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 contents, int hitbmodels, void **hitent)
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]->brushq1.hulls, 0, 0, 1, start, end, &trace);
167 #else
168         RecursiveHullCheck (cl.worldmodel->brushq1.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_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1"};
288 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1"};
289 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5"};
290 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55"};
291 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1"};
292 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1"};
293 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0"};
294 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0"};
295 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20"};
296
297 #ifndef WORKINGLQUAKE
298 static mempool_t *cl_part_mempool;
299 #endif
300
301 void CL_Particles_Clear(void)
302 {
303         cl_numparticles = 0;
304 }
305
306 /*
307 ===============
308 CL_InitParticles
309 ===============
310 */
311 void CL_ReadPointFile_f (void);
312 void CL_Particles_Init (void)
313 {
314         int             i;
315
316         i = COM_CheckParm ("-particles");
317
318         if (i && i < com_argc - 1)
319         {
320                 cl_maxparticles = (int)(atoi(com_argv[i+1]));
321                 if (cl_maxparticles < ABSOLUTE_MIN_PARTICLES)
322                         cl_maxparticles = ABSOLUTE_MIN_PARTICLES;
323         }
324         else
325                 cl_maxparticles = MAX_PARTICLES;
326
327         Cmd_AddCommand ("pointfile", CL_ReadPointFile_f);
328
329         Cvar_RegisterVariable (&cl_particles);
330         Cvar_RegisterVariable (&cl_particles_quality);
331         Cvar_RegisterVariable (&cl_particles_size);
332         Cvar_RegisterVariable (&cl_particles_bloodshowers);
333         Cvar_RegisterVariable (&cl_particles_blood);
334         Cvar_RegisterVariable (&cl_particles_blood_alpha);
335         Cvar_RegisterVariable (&cl_particles_bulletimpacts);
336         Cvar_RegisterVariable (&cl_particles_smoke);
337         Cvar_RegisterVariable (&cl_particles_smoke_alpha);
338         Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
339         Cvar_RegisterVariable (&cl_particles_sparks);
340         Cvar_RegisterVariable (&cl_particles_bubbles);
341         Cvar_RegisterVariable (&cl_decals);
342         Cvar_RegisterVariable (&cl_decals_time);
343         Cvar_RegisterVariable (&cl_decals_fadetime);
344
345 #ifdef WORKINGLQUAKE
346         particles = (particle_t *) Hunk_AllocName(cl_maxparticles * sizeof(particle_t), "particles");
347         freeparticles = (void *) Hunk_AllocName(cl_maxparticles * sizeof(particle_t *), "particles");
348 #else
349         cl_part_mempool = Mem_AllocPool("CL_Part");
350         particles = (particle_t *) Mem_Alloc(cl_part_mempool, cl_maxparticles * sizeof(particle_t));
351         freeparticles = (void *) Mem_Alloc(cl_part_mempool, cl_maxparticles * sizeof(particle_t *));
352 #endif
353         cl_numparticles = 0;
354 }
355
356 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)
357 {
358         if (cl_numparticles < cl_maxparticles)
359         {
360                 particle_t *part;
361                 int ptempcolor, ptempcolor2, pcr1, pcg1, pcb1, pcr2, pcg2, pcb2;
362                 ptempcolor = (pcolor1);
363                 ptempcolor2 = (pcolor2);
364                 pcr2 = ((ptempcolor2) >> 16) & 0xFF;
365                 pcg2 = ((ptempcolor2) >> 8) & 0xFF;
366                 pcb2 = (ptempcolor2) & 0xFF;
367                 if (ptempcolor != ptempcolor2)
368                 {
369                         pcr1 = ((ptempcolor) >> 16) & 0xFF;
370                         pcg1 = ((ptempcolor) >> 8) & 0xFF;
371                         pcb1 = (ptempcolor) & 0xFF;
372                         ptempcolor = rand() & 0xFF;
373                         pcr2 = (((pcr2 - pcr1) * ptempcolor) >> 8) + pcr1;
374                         pcg2 = (((pcg2 - pcg1) * ptempcolor) >> 8) + pcg1;
375                         pcb2 = (((pcb2 - pcb1) * ptempcolor) >> 8) + pcb1;
376                 }
377                 part = &particles[cl_numparticles++];
378                 memset(part, 0, sizeof(*part));
379                 part->type = (ptype);
380                 part->color[0] = pcr2;
381                 part->color[1] = pcg2;
382                 part->color[2] = pcb2;
383                 part->color[3] = 0xFF;
384                 part->orientation = porientation;
385                 part->texnum = ptex;
386                 part->blendmode = pblendmode;
387                 part->scalex = (pscalex);
388                 part->scaley = (pscaley);
389                 part->alpha = (palpha);
390                 part->alphafade = (palphafade);
391                 part->die = cl.time + (ptime);
392                 part->gravity = (pgravity);
393                 part->bounce = (pbounce);
394                 part->org[0] = (px);
395                 part->org[1] = (py);
396                 part->org[2] = (pz);
397                 part->vel[0] = (pvx);
398                 part->vel[1] = (pvy);
399                 part->vel[2] = (pvz);
400                 part->time2 = (ptime2);
401                 part->vel2[0] = (pvx2);
402                 part->vel2[1] = (pvy2);
403                 part->vel2[2] = (pvz2);
404                 part->friction = (pfriction);
405                 part->pressure = (ppressure);
406                 return part;
407         }
408         return NULL;
409 }
410
411 void CL_SpawnDecalParticleForSurface(void *hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
412 {
413         particle_t *p;
414         if (!cl_decals.integer)
415                 return;
416         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);
417 #ifndef WORKINGLQUAKE
418         if (p)
419         {
420                 p->owner = hitent;
421                 p->ownermodel = p->owner->model;
422                 Matrix4x4_Transform(&p->owner->inversematrix, org, p->relativeorigin);
423                 Matrix4x4_Transform3x3(&p->owner->inversematrix, normal, p->relativedirection);
424                 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
425         }
426 #endif
427 }
428
429 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
430 {
431         int i;
432         float bestfrac, bestorg[3], bestnormal[3];
433         float frac, v[3], normal[3], org2[3];
434         void *besthitent;
435 #ifdef WORKINGLQUAKE
436         void *hitent;
437 #else
438         entity_render_t *hitent;
439 #endif
440         bestfrac = 10;
441         for (i = 0;i < 32;i++)
442         {
443                 VectorRandom(org2);
444                 VectorMA(org, maxdist, org2, org2);
445                 frac = CL_TraceLine(org, org2, v, normal, 0, true, &hitent);
446                 if (bestfrac > frac)
447                 {
448                         bestfrac = frac;
449                         besthitent = hitent;
450                         VectorCopy(v, bestorg);
451                         VectorCopy(normal, bestnormal);
452                 }
453         }
454         if (bestfrac < 1)
455                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
456 }
457
458 /*
459 ===============
460 CL_EntityParticles
461 ===============
462 */
463 void CL_EntityParticles (entity_t *ent)
464 {
465         int                     i;
466         float           angle;
467         float           sp, sy, cp, cy;
468         vec3_t          forward;
469         float           dist;
470         float           beamlength;
471         static vec3_t avelocities[NUMVERTEXNORMALS];
472         if (!cl_particles.integer) return;
473
474         dist = 64;
475         beamlength = 16;
476
477         if (!avelocities[0][0])
478                 for (i=0 ; i<NUMVERTEXNORMALS*3 ; i++)
479                         avelocities[0][i] = (rand()&255) * 0.01;
480
481         for (i=0 ; i<NUMVERTEXNORMALS ; i++)
482         {
483                 angle = cl.time * avelocities[i][0];
484                 sy = sin(angle);
485                 cy = cos(angle);
486                 angle = cl.time * avelocities[i][1];
487                 sp = sin(angle);
488                 cp = cos(angle);
489
490                 forward[0] = cp*cy;
491                 forward[1] = cp*sy;
492                 forward[2] = -sp;
493
494 #ifdef WORKINGLQUAKE
495                 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);
496 #else
497                 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);
498 #endif
499         }
500 }
501
502
503 void CL_ReadPointFile_f (void)
504 {
505         vec3_t org, leakorg;
506         int r, c, s;
507         char *pointfile = NULL, *pointfilepos, *t, tchar;
508         char name[MAX_OSPATH];
509
510         if (!cl.worldmodel)
511                 return;
512
513         FS_StripExtension(cl.worldmodel->name, name);
514         strcat(name, ".pts");
515 #if WORKINGLQUAKE
516         pointfile = COM_LoadTempFile (name);
517 #else
518         pointfile = FS_LoadFile(name, true);
519 #endif
520         if (!pointfile)
521         {
522                 Con_Printf ("Could not open %s\n", name);
523                 return;
524         }
525
526         Con_Printf ("Reading %s...\n", name);
527         c = 0;
528         s = 0;
529         pointfilepos = pointfile;
530         while (*pointfilepos)
531         {
532                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
533                         pointfilepos++;
534                 if (!*pointfilepos)
535                         break;
536                 t = pointfilepos;
537                 while (*t && *t != '\n' && *t != '\r')
538                         t++;
539                 tchar = *t;
540                 *t = 0;
541                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
542                 *t = tchar;
543                 pointfilepos = t;
544                 if (r != 3)
545                         break;
546                 if (c == 0)
547                         VectorCopy(org, leakorg);
548                 c++;
549
550                 if (cl_numparticles < cl_maxparticles - 3)
551                 {
552                         s++;
553                         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);
554                 }
555         }
556 #ifndef WORKINGLQUAKE
557         Mem_Free(pointfile);
558 #endif
559         VectorCopy(leakorg, org);
560         Con_Printf ("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
561
562         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);
563         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);
564         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);
565 }
566
567 /*
568 ===============
569 CL_ParseParticleEffect
570
571 Parse an effect out of the server message
572 ===============
573 */
574 void CL_ParseParticleEffect (void)
575 {
576         vec3_t org, dir;
577         int i, count, msgcount, color;
578
579         for (i=0 ; i<3 ; i++)
580                 org[i] = MSG_ReadCoord ();
581         for (i=0 ; i<3 ; i++)
582                 dir[i] = MSG_ReadChar () * (1.0/16);
583         msgcount = MSG_ReadByte ();
584         color = MSG_ReadByte ();
585
586         if (msgcount == 255)
587                 count = 1024;
588         else
589                 count = msgcount;
590
591         CL_RunParticleEffect (org, dir, color, count);
592 }
593
594 /*
595 ===============
596 CL_ParticleExplosion
597
598 ===============
599 */
600 void CL_ParticleExplosion (vec3_t org)
601 {
602         int i, k;
603         //vec3_t v;
604         //vec3_t v2;
605         if (cl_stainmaps.integer)
606                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
607         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
608
609         i = CL_PointContents(org);
610         if ((i == CONTENTS_SLIME || i == CONTENTS_WATER) && cl_particles.integer && cl_particles_bubbles.integer)
611         {
612                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
613                         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);
614         }
615         else
616         {
617                 /*
618                 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
619                 // smoke puff
620                 if (cl_particles.integer && cl_particles_smoke.integer)
621                 {
622                         for (i = 0;i < 64;i++)
623                         {
624 #ifdef WORKINGLQUAKE
625                                 v2[0] = lhrandom(-64, 64);
626                                 v2[1] = lhrandom(-64, 64);
627                                 v2[2] = lhrandom(-8, 24);
628 #else
629                                 for (k = 0;k < 16;k++)
630                                 {
631                                         v[0] = org[0] + lhrandom(-64, 64);
632                                         v[1] = org[1] + lhrandom(-64, 64);
633                                         v[2] = org[2] + lhrandom(-8, 24);
634                                         if (CL_TraceLine(org, v, v2, NULL, 0, true, NULL) >= 0.1)
635                                                 break;
636                                 }
637                                 VectorSubtract(v2, org, v2);
638 #endif
639                                 VectorScale(v2, 2.0f, v2);
640                                 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);
641                         }
642                 }
643                 */
644
645                 if (cl_particles.integer && cl_particles_sparks.integer)
646                 {
647                         // sparks
648                         for (i = 0;i < 256 * cl_particles_quality.value;i++)
649                         {
650                                 k = particlepalette[0x68 + (rand() & 7)];
651                                 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);
652                         }
653                 }
654         }
655
656         if (cl_explosions.integer)
657                 R_NewExplosion(org);
658 }
659
660 /*
661 ===============
662 CL_ParticleExplosion2
663
664 ===============
665 */
666 void CL_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength)
667 {
668         int i, k;
669         if (!cl_particles.integer) return;
670
671         for (i = 0;i < 512 * cl_particles_quality.value;i++)
672         {
673                 k = particlepalette[colorStart + (i % colorLength)];
674                 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);
675         }
676 }
677
678 /*
679 ===============
680 CL_BlobExplosion
681
682 ===============
683 */
684 void CL_BlobExplosion (vec3_t org)
685 {
686         if (cl_stainmaps.integer)
687                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
688         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
689
690         if (cl_explosions.integer)
691                 R_NewExplosion(org);
692 }
693
694 /*
695 ===============
696 CL_RunParticleEffect
697
698 ===============
699 */
700 void CL_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
701 {
702         int k;
703
704         if (count == 1024)
705         {
706                 CL_ParticleExplosion(org);
707                 return;
708         }
709         if (!cl_particles.integer) return;
710         count *= cl_particles_quality.value;
711         while (count--)
712         {
713                 k = particlepalette[color + (rand()&7)];
714                 if (gamemode == GAME_GOODVSBAD2)
715                         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);
716                 else
717                         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);
718         }
719 }
720
721 // LordHavoc: added this for spawning sparks/dust (which have strong gravity)
722 /*
723 ===============
724 CL_SparkShower
725 ===============
726 */
727 void CL_SparkShower (vec3_t org, vec3_t dir, int count)
728 {
729         vec3_t org2, org3;
730         int k;
731
732         if (cl_stainmaps.integer)
733                 R_Stain(org, 32, 96, 96, 96, 24, 128, 128, 128, 24);
734         CL_SpawnDecalParticleForPoint(org, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
735
736         if (!cl_particles.integer) return;
737
738         if (cl_particles_bulletimpacts.integer)
739         {
740                 // smoke puff
741                 if (cl_particles_smoke.integer)
742                 {
743                         k = count * 0.25 * cl_particles_quality.value;
744                         while(k--)
745                         {
746                                 org2[0] = org[0] + 0.125f * lhrandom(-count, count);
747                                 org2[1] = org[1] + 0.125f * lhrandom(-count, count);
748                                 org2[2] = org[2] + 0.125f * lhrandom(-count, count);
749                                 CL_TraceLine(org, org2, org3, NULL, 0, true, NULL);
750                                 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);
751                         }
752                 }
753
754                 if (cl_particles_sparks.integer)
755                 {
756                         // sparks
757                         count *= cl_particles_quality.value;
758                         while(count--)
759                         {
760                                 k = particlepalette[0x68 + (rand() & 7)];
761                                 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);
762                         }
763                 }
764         }
765 }
766
767 void CL_PlasmaBurn (vec3_t org)
768 {
769         if (cl_stainmaps.integer)
770                 R_Stain(org, 48, 96, 96, 96, 32, 128, 128, 128, 32);
771         CL_SpawnDecalParticleForPoint(org, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
772 }
773
774 static float bloodcount = 0;
775 void CL_BloodPuff (vec3_t org, vec3_t vel, int count)
776 {
777         float s;
778         vec3_t org2, org3;
779         // bloodcount is used to accumulate counts too small to cause a blood particle
780         if (!cl_particles.integer) return;
781         if (!cl_particles_blood.integer) return;
782
783         s = count + 32.0f;
784         count *= 5.0f;
785         if (count > 1000)
786                 count = 1000;
787         bloodcount += count;
788         while(bloodcount > 0)
789         {
790                 org2[0] = org[0] + 0.125f * lhrandom(-bloodcount, bloodcount);
791                 org2[1] = org[1] + 0.125f * lhrandom(-bloodcount, bloodcount);
792                 org2[2] = org[2] + 0.125f * lhrandom(-bloodcount, bloodcount);
793                 CL_TraceLine(org, org2, org3, NULL, 0, true, NULL);
794                 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);
795                 bloodcount -= 16 / cl_particles_quality.value;
796         }
797 }
798
799 void CL_BloodShower (vec3_t mins, vec3_t maxs, float velspeed, int count)
800 {
801         vec3_t org, vel, diff, center, velscale;
802         if (!cl_particles.integer) return;
803         if (!cl_particles_bloodshowers.integer) return;
804         if (!cl_particles_blood.integer) return;
805
806         VectorSubtract(maxs, mins, diff);
807         center[0] = (mins[0] + maxs[0]) * 0.5;
808         center[1] = (mins[1] + maxs[1]) * 0.5;
809         center[2] = (mins[2] + maxs[2]) * 0.5;
810         velscale[0] = velspeed * 2.0 / diff[0];
811         velscale[1] = velspeed * 2.0 / diff[1];
812         velscale[2] = velspeed * 2.0 / diff[2];
813
814         bloodcount += count * 5.0f;
815         while (bloodcount > 0)
816         {
817                 org[0] = lhrandom(mins[0], maxs[0]);
818                 org[1] = lhrandom(mins[1], maxs[1]);
819                 org[2] = lhrandom(mins[2], maxs[2]);
820                 vel[0] = (org[0] - center[0]) * velscale[0];
821                 vel[1] = (org[1] - center[1]) * velscale[1];
822                 vel[2] = (org[2] - center[2]) * velscale[2];
823                 bloodcount -= 16 / cl_particles_quality.value;
824                 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);
825         }
826 }
827
828 void CL_ParticleCube (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int gravity, int randomvel)
829 {
830         int k;
831         float t;
832         if (!cl_particles.integer) return;
833         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
834         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
835         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
836
837         count *= cl_particles_quality.value;
838         while (count--)
839         {
840                 k = particlepalette[colorbase + (rand()&3)];
841                 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);
842         }
843 }
844
845 void CL_ParticleRain (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int type)
846 {
847         int k;
848         float t, z, minz, maxz;
849         if (!cl_particles.integer) return;
850         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
851         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
852         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
853         if (dir[2] < 0) // falling
854         {
855                 t = (maxs[2] - mins[2]) / -dir[2];
856                 z = maxs[2];
857         }
858         else // rising??
859         {
860                 t = (maxs[2] - mins[2]) / dir[2];
861                 z = mins[2];
862         }
863         if (t < 0 || t > 2) // sanity check
864                 t = 2;
865
866         minz = z - fabs(dir[2]) * 0.1;
867         maxz = z + fabs(dir[2]) * 0.1;
868         minz = bound(mins[2], minz, maxs[2]);
869         maxz = bound(mins[2], maxz, maxs[2]);
870
871         count *= cl_particles_quality.value;
872
873         switch(type)
874         {
875         case 0:
876                 count *= 4; // ick, this should be in the mod or maps?
877
878                 while(count--)
879                 {
880                         k = particlepalette[colorbase + (rand()&3)];
881                         if (gamemode == GAME_GOODVSBAD2)
882                         {
883                                 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);
884                         }
885                         else
886                         {
887                                 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);
888                         }
889                 }
890                 break;
891         case 1:
892                 while(count--)
893                 {
894                         k = particlepalette[colorbase + (rand()&3)];
895                         if (gamemode == GAME_GOODVSBAD2)
896                         {
897                                 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);
898                         }
899                         else
900                         {
901                                 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);
902                         }
903                 }
904                 break;
905         default:
906                 Host_Error("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
907         }
908 }
909
910 void CL_Stardust (vec3_t mins, vec3_t maxs, int count)
911 {
912         int k;
913         float t;
914         vec3_t o, v, center;
915         if (!cl_particles.integer) return;
916
917         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
918         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
919         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
920
921         center[0] = (mins[0] + maxs[0]) * 0.5f;
922         center[1] = (mins[1] + maxs[1]) * 0.5f;
923         center[2] = (mins[2] + maxs[2]) * 0.5f;
924
925         count *= cl_particles_quality.value;
926         while (count--)
927         {
928                 k = particlepalette[224 + (rand()&15)];
929                 o[0] = lhrandom(mins[0], maxs[0]);
930                 o[1] = lhrandom(mins[1], maxs[1]);
931                 o[2] = lhrandom(mins[2], maxs[2]);
932                 VectorSubtract(o, center, v);
933                 VectorNormalizeFast(v);
934                 VectorScale(v, 100, v);
935                 v[2] += sv_gravity.value * 0.15f;
936                 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);
937         }
938 }
939
940 void CL_FlameCube (vec3_t mins, vec3_t maxs, int count)
941 {
942         int k;
943         float t;
944         if (!cl_particles.integer) return;
945         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
946         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
947         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
948
949         count *= cl_particles_quality.value;
950         while (count--)
951         {
952                 k = particlepalette[224 + (rand()&15)];
953                 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);
954                 if (count & 1)
955                         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);
956         }
957 }
958
959 void CL_Flames (vec3_t org, vec3_t vel, int count)
960 {
961         int k;
962         if (!cl_particles.integer) return;
963
964         count *= cl_particles_quality.value;
965         while (count--)
966         {
967                 k = particlepalette[224 + (rand()&15)];
968                 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);
969         }
970 }
971
972
973
974 /*
975 ===============
976 CL_LavaSplash
977
978 ===============
979 */
980 void CL_LavaSplash (vec3_t origin)
981 {
982         float i, j, inc, vel;
983         int k, l;
984         vec3_t          dir, org;
985         if (!cl_particles.integer) return;
986
987         inc = 32 / cl_particles_quality.value;
988         for (i = -128;i < 128;i += inc)
989         {
990                 for (j = -128;j < 128;j += inc)
991                 {
992                         dir[0] = j + lhrandom(0, 8);
993                         dir[1] = i + lhrandom(0, 8);
994                         dir[2] = 256;
995                         org[0] = origin[0] + dir[0];
996                         org[1] = origin[1] + dir[1];
997                         org[2] = origin[2] + lhrandom(0, 64);
998                         vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
999                         if (gamemode == GAME_GOODVSBAD2)
1000                         {
1001                                 k = particlepalette[0 + (rand()&255)];
1002                                 l = particlepalette[0 + (rand()&255)];
1003                                 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);
1004                         }
1005                         else
1006                         {
1007                                 k = l = particlepalette[224 + (rand()&7)];
1008                                 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);
1009                         }
1010                 }
1011         }
1012 }
1013
1014 /*
1015 ===============
1016 CL_TeleportSplash
1017
1018 ===============
1019 */
1020 #if WORKINGLQUAKE
1021 void R_TeleportSplash (vec3_t org)
1022 {
1023         float i, j, k, inc;
1024         if (!cl_particles.integer) return;
1025
1026         inc = 8 / cl_particles_quality.value;
1027         for (i = -16;i < 16;i += inc)
1028                 for (j = -16;j < 16;j += inc)
1029                         for (k = -24;k < 32;k += inc)
1030                                 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);
1031 }
1032 #endif
1033
1034 #ifdef WORKINGLQUAKE
1035 void R_RocketTrail (vec3_t start, vec3_t end, int type)
1036 #else
1037 void CL_RocketTrail (vec3_t start, vec3_t end, int type, entity_t *ent)
1038 #endif
1039 {
1040         vec3_t vec, dir, vel, pos;
1041         float len, dec, speed, qd;
1042         int contents, smoke, blood, bubbles;
1043
1044         if (end[0] == start[0] && end[1] == start[1] && end[2] == start[2])
1045                 return;
1046
1047         VectorSubtract(end, start, dir);
1048         VectorNormalize(dir);
1049
1050         VectorSubtract (end, start, vec);
1051 #ifdef WORKINGLQUAKE
1052         len = VectorNormalize (vec);
1053         dec = 0;
1054         speed = 1.0f / cl.frametime;
1055         VectorSubtract(end, start, vel);
1056 #else
1057         len = VectorNormalizeLength (vec);
1058         dec = -ent->persistent.trail_time;
1059         ent->persistent.trail_time += len;
1060         if (ent->persistent.trail_time < 0.01f)
1061                 return;
1062
1063         // if we skip out, leave it reset
1064         ent->persistent.trail_time = 0.0f;
1065
1066         speed = 1.0f / (ent->state_current.time - ent->state_previous.time);
1067         VectorSubtract(ent->state_current.origin, ent->state_previous.origin, vel);
1068 #endif
1069         VectorScale(vel, speed, vel);
1070
1071         // advance into this frame to reach the first puff location
1072         VectorMA(start, dec, vec, pos);
1073         len -= dec;
1074
1075         contents = CL_PointContents(pos);
1076         if (contents == CONTENTS_SKY || contents == CONTENTS_LAVA)
1077                 return;
1078
1079         smoke = cl_particles.integer && cl_particles_smoke.integer;
1080         blood = cl_particles.integer && cl_particles_blood.integer;
1081         bubbles = cl_particles.integer && cl_particles_bubbles.integer && (contents == CONTENTS_WATER || contents == CONTENTS_SLIME);
1082         qd = 1.0f / cl_particles_quality.value;
1083
1084         while (len >= 0)
1085         {
1086                 switch (type)
1087                 {
1088                         case 0: // rocket trail
1089                                 dec = qd*3;
1090                                 if (smoke)
1091                                 {
1092                                         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);
1093                                         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);
1094                                 }
1095                                 if (bubbles)
1096                                         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);
1097                                 break;
1098
1099                         case 1: // grenade trail
1100                                 // FIXME: make it gradually stop smoking
1101                                 dec = qd*3;
1102                                 if (smoke)
1103                                         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);
1104                                 break;
1105
1106
1107                         case 2: // blood
1108                         case 4: // slight blood
1109                                 dec = qd*16;
1110                                 if (blood)
1111                                         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);
1112                                 break;
1113
1114                         case 3: // green tracer
1115                                 dec = qd*6;
1116                                 if (smoke)
1117                                 {
1118                                         if (gamemode == GAME_GOODVSBAD2)
1119                                                 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);
1120                                         else
1121                                                 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);
1122                                 }
1123                                 break;
1124
1125                         case 5: // flame tracer
1126                                 dec = qd*6;
1127                                 if (smoke)
1128                                         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);
1129                                 break;
1130
1131                         case 6: // voor trail
1132                                 dec = qd*6;
1133                                 if (smoke)
1134                                 {
1135                                         if (gamemode == GAME_GOODVSBAD2)
1136                                                 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);
1137                                         else
1138                                                 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);
1139                                 }
1140                                 break;
1141
1142                         case 7: // Nehahra smoke tracer
1143                                 dec = qd*7;
1144                                 if (smoke)
1145                                         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);
1146                                 break;
1147                         case 8: // Nexuiz plasma trail
1148                                 dec = qd*4;
1149                                 if (smoke)
1150                                         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);
1151                                 break;
1152                 }
1153
1154                 // advance to next time and position
1155                 len -= dec;
1156                 VectorMA (pos, dec, vec, pos);
1157         }
1158 #ifndef WORKINGLQUAKE
1159         ent->persistent.trail_time = len;
1160 #endif
1161 }
1162
1163 void CL_RocketTrail2 (vec3_t start, vec3_t end, int color, entity_t *ent)
1164 {
1165         float dec, len;
1166         vec3_t vec, pos;
1167         if (!cl_particles.integer) return;
1168         if (!cl_particles_smoke.integer) return;
1169
1170         VectorCopy(start, pos);
1171         VectorSubtract(end, start, vec);
1172 #ifdef WORKINGLQUAKE
1173         len = VectorNormalize(vec);
1174 #else
1175         len = VectorNormalizeLength(vec);
1176 #endif
1177         color = particlepalette[color];
1178         dec = 3.0f / cl_particles_quality.value;
1179         while (len > 0)
1180         {
1181                 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);
1182                 len -= dec;
1183                 VectorMA(pos, dec, vec, pos);
1184         }
1185 }
1186
1187 void CL_BeamParticle (const vec3_t start, const vec3_t end, vec_t radius, float red, float green, float blue, float alpha, float lifetime)
1188 {
1189         int tempcolor2, cr, cg, cb;
1190         cr = red * 255;
1191         cg = green * 255;
1192         cb = blue * 255;
1193         tempcolor2 = (bound(0, cr, 255) << 16) | (bound(0, cg, 255) << 8) | bound(0, cb, 255);
1194         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);
1195 }
1196
1197 void CL_Tei_Smoke(const vec3_t org, const vec3_t dir, int count)
1198 {
1199         float f;
1200         if (!cl_particles.integer) return;
1201
1202         // smoke puff
1203         if (cl_particles_smoke.integer)
1204                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1205                         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);
1206 }
1207
1208 void CL_Tei_PlasmaHit(const vec3_t org, const vec3_t dir, int count)
1209 {
1210         float f;
1211         if (!cl_particles.integer) return;
1212
1213         if (cl_stainmaps.integer)
1214                 R_Stain(org, 40, 96, 96, 96, 40, 128, 128, 128, 40);
1215         CL_SpawnDecalParticleForPoint(org, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1216
1217         // smoke puff
1218         if (cl_particles_smoke.integer)
1219                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1220                         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);
1221
1222         // sparks
1223         if (cl_particles_sparks.integer)
1224                 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1225                         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);
1226 }
1227
1228 /*
1229 ===============
1230 CL_MoveParticles
1231 ===============
1232 */
1233 void CL_MoveParticles (void)
1234 {
1235         particle_t *p;
1236         int i, activeparticles, maxparticle, j, a, pressureused = false, content;
1237         float gravity, dvel, bloodwaterfade, frametime, f, dist, normal[3], v[3], org[3];
1238 #ifdef WORKINGLQUAKE
1239         void *hitent;
1240 #else
1241         entity_render_t *hitent;
1242 #endif
1243
1244         // LordHavoc: early out condition
1245         if (!cl_numparticles)
1246                 return;
1247
1248 #ifdef WORKINGLQUAKE
1249         frametime = cl.frametime;
1250 #else
1251         frametime = cl.time - cl.oldtime;
1252 #endif
1253         gravity = frametime * sv_gravity.value;
1254         dvel = 1+4*frametime;
1255         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1256
1257         activeparticles = 0;
1258         maxparticle = -1;
1259         j = 0;
1260         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1261         {
1262                 content = 0;
1263                 VectorCopy(p->org, p->oldorg);
1264                 VectorMA(p->org, frametime, p->vel, p->org);
1265                 VectorCopy(p->org, org);
1266                 if (p->bounce)
1267                 {
1268                         if (CL_TraceLine(p->oldorg, p->org, v, normal, 0, true, &hitent) < 1)
1269                         {
1270                                 VectorCopy(v, p->org);
1271                                 if (p->bounce < 0)
1272                                 {
1273                                         // assume it's blood (lame, but...)
1274 #ifndef WORKINGLQUAKE
1275                                         if (cl_stainmaps.integer)
1276                                                 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));
1277 #endif
1278                                         if (cl_decals.integer)
1279                                         {
1280                                                 p->type = pt_decal;
1281                                                 p->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
1282                                                 // convert from a blood particle to a blood decal
1283                                                 p->texnum = tex_blooddecal[rand()&7];
1284 #ifndef WORKINGLQUAKE
1285                                                 p->owner = hitent;
1286                                                 p->ownermodel = hitent->model;
1287                                                 Matrix4x4_Transform(&hitent->inversematrix, v, p->relativeorigin);
1288                                                 Matrix4x4_Transform3x3(&hitent->inversematrix, normal, p->relativedirection);
1289                                                 VectorAdd(p->relativeorigin, p->relativedirection, p->relativeorigin);
1290 #endif
1291                                                 p->time2 = cl.time + cl_decals_time.value;
1292                                                 p->die = p->time2 + cl_decals_fadetime.value;
1293                                                 p->alphafade = 0;
1294                                                 VectorCopy(normal, p->vel2);
1295                                                 VectorClear(p->vel);
1296                                                 VectorAdd(p->org, normal, p->org);
1297                                                 p->bounce = 0;
1298                                                 p->friction = 0;
1299                                                 p->gravity = 0;
1300                                                 p->scalex *= 1.25f;
1301                                                 p->scaley *= 1.25f;
1302                                         }
1303                                         else
1304                                         {
1305                                                 p->die = -1;
1306                                                 freeparticles[j++] = p;
1307                                                 continue;
1308                                         }
1309                                 }
1310                                 else
1311                                 {
1312                                         dist = DotProduct(p->vel, normal) * -p->bounce;
1313                                         VectorMA(p->vel, dist, normal, p->vel);
1314                                         if (DotProduct(p->vel, p->vel) < 0.03)
1315                                                 VectorClear(p->vel);
1316                                 }
1317                         }
1318                 }
1319                 p->vel[2] -= p->gravity * gravity;
1320                 p->alpha -= p->alphafade * frametime;
1321                 if (p->friction)
1322                 {
1323                         f = p->friction * frametime;
1324                         if (!content)
1325                                 content = CL_PointContents(p->org);
1326                         if (content != CONTENTS_EMPTY)
1327                                 f *= 4;
1328                         f = 1.0f - f;
1329                         VectorScale(p->vel, f, p->vel);
1330                 }
1331
1332                 if (p->type != pt_static)
1333                 {
1334                         switch (p->type)
1335                         {
1336                         case pt_blood:
1337                                 if (!content)
1338                                         content = CL_PointContents(p->org);
1339                                 a = content;
1340                                 if (a != CONTENTS_EMPTY)
1341                                 {
1342                                         if (a == CONTENTS_WATER || a == CONTENTS_SLIME)
1343                                         {
1344                                                 p->scalex += frametime * 8;
1345                                                 p->scaley += frametime * 8;
1346                                                 //p->alpha -= bloodwaterfade;
1347                                         }
1348                                         else
1349                                                 p->die = -1;
1350                                 }
1351                                 else
1352                                         p->vel[2] -= gravity;
1353                                 break;
1354                         case pt_bubble:
1355                                 if (!content)
1356                                         content = CL_PointContents(p->org);
1357                                 if (content != CONTENTS_WATER && content != CONTENTS_SLIME)
1358                                 {
1359                                         p->die = -1;
1360                                         break;
1361                                 }
1362                                 break;
1363                         case pt_rain:
1364                                 if (cl.time > p->time2)
1365                                 {
1366                                         // snow flutter
1367                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1368                                         p->vel[0] = lhrandom(-32, 32) + p->vel2[0];
1369                                         p->vel[1] = lhrandom(-32, 32) + p->vel2[1];
1370                                         p->vel[2] = /*lhrandom(-32, 32) +*/ p->vel2[2];
1371                                 }
1372                                 if (!content)
1373                                         content = CL_PointContents(p->org);
1374                                 a = content;
1375                                 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1376                                         p->die = -1;
1377                                 break;
1378                         case pt_grow:
1379                                 p->scalex += frametime * p->time2;
1380                                 p->scaley += frametime * p->time2;
1381                                 break;
1382                         case pt_decal:
1383 #ifndef WORKINGLQUAKE
1384                                 if (p->owner->model == p->ownermodel)
1385                                 {
1386                                         Matrix4x4_Transform(&p->owner->matrix, p->relativeorigin, p->org);
1387                                         Matrix4x4_Transform3x3(&p->owner->matrix, p->relativedirection, p->vel2);
1388                                 }
1389                                 else
1390                                         p->die = -1;
1391 #endif
1392                                 if (cl.time > p->time2)
1393                                 {
1394                                         p->alphafade = p->alpha / (p->die - cl.time);
1395                                         p->type = pt_decalfade;
1396                                 }
1397                                 break;
1398                         case pt_decalfade:
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                                 break;
1409                         default:
1410                                 Con_Printf("unknown particle type %i\n", p->type);
1411                                 p->die = -1;
1412                                 break;
1413                         }
1414                 }
1415
1416                 // remove dead particles
1417                 if (p->alpha < 1 || p->die < cl.time)
1418                         freeparticles[j++] = p;
1419                 else
1420                 {
1421                         maxparticle = i;
1422                         activeparticles++;
1423                         if (p->pressure)
1424                                 pressureused = true;
1425                 }
1426         }
1427         // fill in gaps to compact the array
1428         i = 0;
1429         while (maxparticle >= activeparticles)
1430         {
1431                 *freeparticles[i++] = particles[maxparticle--];
1432                 while (maxparticle >= activeparticles && particles[maxparticle].die < cl.time)
1433                         maxparticle--;
1434         }
1435         cl_numparticles = activeparticles;
1436
1437         if (pressureused)
1438         {
1439                 activeparticles = 0;
1440                 for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1441                         if (p->pressure)
1442                                 freeparticles[activeparticles++] = p;
1443
1444                 if (activeparticles)
1445                 {
1446                         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1447                         {
1448                                 for (j = 0;j < activeparticles;j++)
1449                                 {
1450                                         if (freeparticles[j] != p)
1451                                         {
1452                                                 float dist, diff[3];
1453                                                 VectorSubtract(p->org, freeparticles[j]->org, diff);
1454                                                 dist = DotProduct(diff, diff);
1455                                                 if (dist < 4096 && dist >= 1)
1456                                                 {
1457                                                         dist = freeparticles[j]->scalex * 4.0f * frametime / sqrt(dist);
1458                                                         VectorMA(p->vel, dist, diff, p->vel);
1459                                                 }
1460                                         }
1461                                 }
1462                         }
1463                 }
1464         }
1465 }
1466
1467 #define MAX_PARTICLETEXTURES 64
1468 // particletexture_t is a rectangle in the particlefonttexture
1469 typedef struct
1470 {
1471         rtexture_t *texture;
1472         float s1, t1, s2, t2;
1473 }
1474 particletexture_t;
1475
1476 #if WORKINGLQUAKE
1477 static int particlefonttexture;
1478 #else
1479 static rtexturepool_t *particletexturepool;
1480 static rtexture_t *particlefonttexture;
1481 #endif
1482 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1483
1484 static cvar_t r_drawparticles = {0, "r_drawparticles", "1"};
1485
1486 static qbyte shadebubble(float dx, float dy, vec3_t light)
1487 {
1488         float dz, f, dot;
1489         vec3_t normal;
1490         dz = 1 - (dx*dx+dy*dy);
1491         if (dz > 0) // it does hit the sphere
1492         {
1493                 f = 0;
1494                 // back side
1495                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1496                 VectorNormalize(normal);
1497                 dot = DotProduct(normal, light);
1498                 if (dot > 0.5) // interior reflection
1499                         f += ((dot *  2) - 1);
1500                 else if (dot < -0.5) // exterior reflection
1501                         f += ((dot * -2) - 1);
1502                 // front side
1503                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1504                 VectorNormalize(normal);
1505                 dot = DotProduct(normal, light);
1506                 if (dot > 0.5) // interior reflection
1507                         f += ((dot *  2) - 1);
1508                 else if (dot < -0.5) // exterior reflection
1509                         f += ((dot * -2) - 1);
1510                 f *= 128;
1511                 f += 16; // just to give it a haze so you can see the outline
1512                 f = bound(0, f, 255);
1513                 return (qbyte) f;
1514         }
1515         else
1516                 return 0;
1517 }
1518
1519 static void setuptex(int texnum, qbyte *data, qbyte *particletexturedata)
1520 {
1521         int basex, basey, y;
1522         basex = ((texnum >> 0) & 7) * 32;
1523         basey = ((texnum >> 3) & 7) * 32;
1524         particletexture[texnum].s1 = (basex + 1) / 256.0f;
1525         particletexture[texnum].t1 = (basey + 1) / 256.0f;
1526         particletexture[texnum].s2 = (basex + 31) / 256.0f;
1527         particletexture[texnum].t2 = (basey + 31) / 256.0f;
1528         for (y = 0;y < 32;y++)
1529                 memcpy(particletexturedata + ((basey + y) * 256 + basex) * 4, data + y * 32 * 4, 32 * 4);
1530 }
1531
1532 void particletextureblotch(qbyte *data, float radius, float red, float green, float blue, float alpha)
1533 {
1534         int x, y;
1535         float cx, cy, dx, dy, f, iradius;
1536         qbyte *d;
1537         cx = lhrandom(radius + 1, 30 - radius);
1538         cy = lhrandom(radius + 1, 30 - radius);
1539         iradius = 1.0f / radius;
1540         alpha *= (1.0f / 255.0f);
1541         for (y = 0;y < 32;y++)
1542         {
1543                 for (x = 0;x < 32;x++)
1544                 {
1545                         dx = (x - cx);
1546                         dy = (y - cy);
1547                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1548                         if (f > 0)
1549                         {
1550                                 d = data + (y * 32 + x) * 4;
1551                                 d[0] += f * (red   - d[0]);
1552                                 d[1] += f * (green - d[1]);
1553                                 d[2] += f * (blue  - d[2]);
1554                         }
1555                 }
1556         }
1557 }
1558
1559 void particletextureclamp(qbyte *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1560 {
1561         int i;
1562         for (i = 0;i < 32*32;i++, data += 4)
1563         {
1564                 data[0] = bound(minr, data[0], maxr);
1565                 data[1] = bound(ming, data[1], maxg);
1566                 data[2] = bound(minb, data[2], maxb);
1567         }
1568 }
1569
1570 void particletextureinvert(qbyte *data)
1571 {
1572         int i;
1573         for (i = 0;i < 32*32;i++, data += 4)
1574         {
1575                 data[0] = 255 - data[0];
1576                 data[1] = 255 - data[1];
1577                 data[2] = 255 - data[2];
1578         }
1579 }
1580
1581 static void R_InitParticleTexture (void)
1582 {
1583         int x, y, d, i, j, k, m;
1584         float dx, dy, radius, f, f2;
1585         qbyte data[32][32][4], noise1[64][64], noise2[64][64], data2[64][16][4];
1586         vec3_t light;
1587         qbyte particletexturedata[256*256*4];
1588
1589         // a note: decals need to modulate (multiply) the background color to
1590         // properly darken it (stain), and they need to be able to alpha fade,
1591         // this is a very difficult challenge because it means fading to white
1592         // (no change to background) rather than black (darkening everything
1593         // behind the whole decal polygon), and to accomplish this the texture is
1594         // inverted (dark red blood on white background becomes brilliant cyan
1595         // and white on black background) so we can alpha fade it to black, then
1596         // we invert it again during the blendfunc to make it work...
1597
1598         memset(particletexturedata, 255, sizeof(particletexturedata));
1599
1600         // smoke
1601         for (i = 0;i < 8;i++)
1602         {
1603                 memset(&data[0][0][0], 255, sizeof(data));
1604                 do
1605                 {
1606                         fractalnoise(&noise1[0][0], 64, 4);
1607                         fractalnoise(&noise2[0][0], 64, 8);
1608                         m = 0;
1609                         for (y = 0;y < 32;y++)
1610                         {
1611                                 dy = y - 16;
1612                                 for (x = 0;x < 32;x++)
1613                                 {
1614                                         dx = x - 16;
1615                                         d = (noise2[y][x] - 128) * 3 + 192;
1616                                         if (d > 0)
1617                                                 d = d * (256 - (int) (dx*dx+dy*dy)) / 256;
1618                                         d = (d * noise1[y][x]) >> 7;
1619                                         d = bound(0, d, 255);
1620                                         data[y][x][3] = (qbyte) d;
1621                                         if (m < d)
1622                                                 m = d;
1623                                 }
1624                         }
1625                 }
1626                 while (m < 224);
1627                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1628         }
1629
1630         // rain splash
1631         for (i = 0;i < 16;i++)
1632         {
1633                 memset(&data[0][0][0], 255, sizeof(data));
1634                 radius = i * 3.0f / 16.0f;
1635                 f2 = 255.0f * ((15.0f - i) / 15.0f);
1636                 for (y = 0;y < 32;y++)
1637                 {
1638                         dy = (y - 16) * 0.25f;
1639                         for (x = 0;x < 32;x++)
1640                         {
1641                                 dx = (x - 16) * 0.25f;
1642                                 f = (1.0 - fabs(radius - sqrt(dx*dx+dy*dy))) * f2;
1643                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1644                         }
1645                 }
1646                 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1647         }
1648
1649         // normal particle
1650         memset(&data[0][0][0], 255, sizeof(data));
1651         for (y = 0;y < 32;y++)
1652         {
1653                 dy = y - 16;
1654                 for (x = 0;x < 32;x++)
1655                 {
1656                         dx = x - 16;
1657                         d = (256 - (dx*dx+dy*dy));
1658                         d = bound(0, d, 255);
1659                         data[y][x][3] = (qbyte) d;
1660                 }
1661         }
1662         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1663
1664         // rain
1665         memset(&data[0][0][0], 255, sizeof(data));
1666         light[0] = 1;light[1] = 1;light[2] = 1;
1667         VectorNormalize(light);
1668         for (y = 0;y < 32;y++)
1669                 for (x = 0;x < 32;x++)
1670                         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);
1671         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1672
1673         // bubble
1674         memset(&data[0][0][0], 255, sizeof(data));
1675         light[0] = 1;light[1] = 1;light[2] = 1;
1676         VectorNormalize(light);
1677         for (y = 0;y < 32;y++)
1678                 for (x = 0;x < 32;x++)
1679                         data[y][x][3] = shadebubble((x - 16) * (1.0 / 16.0), (y - 16) * (1.0 / 16.0), light);
1680         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1681
1682         // blood particles
1683         for (i = 0;i < 8;i++)
1684         {
1685                 memset(&data[0][0][0], 255, sizeof(data));
1686                 for (k = 0;k < 24;k++)
1687                         particletextureblotch(&data[0][0][0], 2, 96, 0, 0, 160);
1688                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1689                 particletextureinvert(&data[0][0][0]);
1690                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1691         }
1692
1693         // blood decals
1694         for (i = 0;i < 8;i++)
1695         {
1696                 memset(&data[0][0][0], 255, sizeof(data));
1697                 for (k = 0;k < 24;k++)
1698                         particletextureblotch(&data[0][0][0], 2, 96, 0, 0, 96);
1699                 for (j = 3;j < 7;j++)
1700                         for (k = 0, m = rand() % 12;k < m;k++)
1701                                 particletextureblotch(&data[0][0][0], j, 96, 0, 0, 192);
1702                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1703                 particletextureinvert(&data[0][0][0]);
1704                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1705         }
1706
1707         // bullet decals
1708         for (i = 0;i < 8;i++)
1709         {
1710                 memset(&data[0][0][0], 255, sizeof(data));
1711                 for (k = 0;k < 12;k++)
1712                         particletextureblotch(&data[0][0][0], 2, 0, 0, 0, 128);
1713                 for (k = 0;k < 3;k++)
1714                         particletextureblotch(&data[0][0][0], 14, 0, 0, 0, 160);
1715                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1716                 particletextureinvert(&data[0][0][0]);
1717                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1718         }
1719
1720 #if WORKINGLQUAKE
1721         glBindTexture(GL_TEXTURE_2D, (particlefonttexture = gl_extension_number++));
1722         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1723         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1724 #else
1725         particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", 256, 256, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1726         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1727                 particletexture[i].texture = particlefonttexture;
1728
1729         // beam
1730         fractalnoise(&noise1[0][0], 64, 4);
1731         m = 0;
1732         for (y = 0;y < 64;y++)
1733         {
1734                 for (x = 0;x < 16;x++)
1735                 {
1736                         if (x < 8)
1737                                 d = x;
1738                         else
1739                                 d = (15 - x);
1740                         d = d * d * noise1[y][x] / (7 * 7);
1741                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (qbyte) bound(0, d, 255);
1742                         data2[y][x][3] = 255;
1743                 }
1744         }
1745
1746         particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "beam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1747         particletexture[tex_beam].s1 = 0;
1748         particletexture[tex_beam].t1 = 0;
1749         particletexture[tex_beam].s2 = 1;
1750         particletexture[tex_beam].t2 = 1;
1751 #endif
1752 }
1753
1754 static void r_part_start(void)
1755 {
1756         particletexturepool = R_AllocTexturePool();
1757         R_InitParticleTexture ();
1758 }
1759
1760 static void r_part_shutdown(void)
1761 {
1762         R_FreeTexturePool(&particletexturepool);
1763 }
1764
1765 static void r_part_newmap(void)
1766 {
1767         cl_numparticles = 0;
1768 }
1769
1770 void R_Particles_Init (void)
1771 {
1772         Cvar_RegisterVariable(&r_drawparticles);
1773 #ifdef WORKINGLQUAKE
1774         r_part_start();
1775 #else
1776         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1777 #endif
1778 }
1779
1780 #ifdef WORKINGLQUAKE
1781 void R_InitParticles(void)
1782 {
1783         CL_Particles_Init();
1784         R_Particles_Init();
1785 }
1786 #endif
1787
1788 float particle_vertex3f[12], particle_texcoord2f[8];
1789
1790 #ifdef WORKINGLQUAKE
1791 void R_DrawParticle(particle_t *p)
1792 {
1793 #else
1794 void R_DrawParticleCallback(const void *calldata1, int calldata2)
1795 {
1796         const particle_t *p = calldata1;
1797         rmeshstate_t m;
1798 #endif
1799         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, fogvec[3], cr, cg, cb, ca;
1800         particletexture_t *tex;
1801
1802         VectorCopy(p->org, org);
1803
1804         tex = &particletexture[p->texnum];
1805         cr = p->color[0] * (1.0f / 255.0f);
1806         cg = p->color[1] * (1.0f / 255.0f);
1807         cb = p->color[2] * (1.0f / 255.0f);
1808         ca = p->alpha * (1.0f / 255.0f);
1809         if (p->blendmode == PBLEND_MOD)
1810         {
1811                 cr *= ca;
1812                 cg *= ca;
1813                 cb *= ca;
1814                 cr = min(cr, 1);
1815                 cg = min(cg, 1);
1816                 cb = min(cb, 1);
1817                 ca = 1;
1818         }
1819
1820 #ifndef WORKINGLQUAKE
1821         if (fogenabled && p->blendmode != PBLEND_MOD)
1822         {
1823                 VectorSubtract(org, r_origin, fogvec);
1824                 fog = exp(fogdensity/DotProduct(fogvec,fogvec));
1825                 ifog = 1 - fog;
1826                 cr = cr * ifog;
1827                 cg = cg * ifog;
1828                 cb = cb * ifog;
1829                 if (p->blendmode == 0)
1830                 {
1831                         cr += fogcolor[0] * fog;
1832                         cg += fogcolor[1] * fog;
1833                         cb += fogcolor[2] * fog;
1834                 }
1835         }
1836         cr *= r_colorscale;
1837         cg *= r_colorscale;
1838         cb *= r_colorscale;
1839
1840         GL_Color(cr, cg, cb, ca);
1841
1842         R_Mesh_Matrix(&r_identitymatrix);
1843
1844         memset(&m, 0, sizeof(m));
1845         m.tex[0] = R_GetTexture(tex->texture);
1846         m.pointer_texcoord[0] = particle_texcoord2f;
1847         R_Mesh_State_Texture(&m);
1848
1849         if (p->blendmode == 0)
1850                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1851         else if (p->blendmode == 1)
1852                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
1853         else
1854                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1855         GL_DepthMask(false);
1856         GL_DepthTest(true);
1857         GL_VertexPointer(particle_vertex3f);
1858 #endif
1859         if (p->orientation == PARTICLE_BILLBOARD || p->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1860         {
1861                 if (p->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1862                 {
1863                         // double-sided
1864                         if (DotProduct(p->vel2, r_origin) > DotProduct(p->vel2, org))
1865                         {
1866                                 VectorNegate(p->vel2, v);
1867                                 VectorVectors(v, right, up);
1868                         }
1869                         else
1870                                 VectorVectors(p->vel2, right, up);
1871                         VectorScale(right, p->scalex, right);
1872                         VectorScale(up, p->scaley, up);
1873                 }
1874                 else
1875                 {
1876                         VectorScale(vright, p->scalex, right);
1877                         VectorScale(vup, p->scaley, up);
1878                 }
1879                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
1880                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
1881                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
1882                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
1883                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
1884                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
1885                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
1886                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
1887                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
1888                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
1889                 particle_vertex3f[10] = org[1] + right[1] - up[1];
1890                 particle_vertex3f[11] = org[2] + right[2] - up[2];
1891                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
1892                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
1893                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
1894                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
1895         }
1896         else if (p->orientation == PARTICLE_SPARK)
1897         {
1898                 VectorMA(p->org, -p->scaley, p->vel, v);
1899                 VectorMA(p->org, p->scaley, p->vel, up2);
1900                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, p->scalex);
1901                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
1902                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
1903                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
1904                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
1905         }
1906         else if (p->orientation == PARTICLE_BEAM)
1907         {
1908                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel2, p->scalex);
1909                 VectorSubtract(p->vel2, p->org, up);
1910                 VectorNormalizeFast(up);
1911                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) - cl.time * 0.25;
1912                 v[1] = DotProduct(p->vel2, up) * (1.0f / 64.0f) - cl.time * 0.25;
1913                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
1914                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
1915                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
1916                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
1917         }
1918         else
1919                 Host_Error("R_DrawParticles: unknown particle orientation %i\n", p->orientation);
1920
1921 #if WORKINGLQUAKE
1922         if (p->blendmode == 0)
1923                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1924         else if (p->blendmode == 1)
1925                 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1926         else
1927                 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1928         glColor4f(cr, cg, cb, ca);
1929         glBegin(GL_QUADS);
1930         glTexCoord2f(particle_texcoord2f[0], particle_texcoord2f[1]);glVertex3f(particle_vertex3f[ 0], particle_vertex3f[ 1], particle_vertex3f[ 2]);
1931         glTexCoord2f(particle_texcoord2f[2], particle_texcoord2f[3]);glVertex3f(particle_vertex3f[ 3], particle_vertex3f[ 4], particle_vertex3f[ 5]);
1932         glTexCoord2f(particle_texcoord2f[4], particle_texcoord2f[5]);glVertex3f(particle_vertex3f[ 6], particle_vertex3f[ 7], particle_vertex3f[ 8]);
1933         glTexCoord2f(particle_texcoord2f[6], particle_texcoord2f[7]);glVertex3f(particle_vertex3f[ 9], particle_vertex3f[10], particle_vertex3f[11]);
1934         glEnd();
1935 #else
1936         R_Mesh_Draw(4, 2, polygonelements);
1937 #endif
1938 }
1939
1940 void R_DrawParticles (void)
1941 {
1942         int i;
1943         float minparticledist;
1944         particle_t *p;
1945
1946 #ifdef WORKINGLQUAKE
1947         CL_MoveParticles();
1948 #endif
1949
1950         // LordHavoc: early out conditions
1951         if ((!cl_numparticles) || (!r_drawparticles.integer))
1952                 return;
1953
1954         minparticledist = DotProduct(r_origin, vpn) + 4.0f;
1955
1956 #ifdef WORKINGLQUAKE
1957         glBindTexture(GL_TEXTURE_2D, particlefonttexture);
1958         glEnable(GL_BLEND);
1959         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1960         glDepthMask(0);
1961         // LordHavoc: only render if not too close
1962         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1963                 if (DotProduct(p->org, vpn) >= minparticledist)
1964                         R_DrawParticle(p);
1965         glDepthMask(1);
1966         glDisable(GL_BLEND);
1967         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1968 #else
1969         // LordHavoc: only render if not too close
1970         c_particles += cl_numparticles;
1971         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1972                 if (DotProduct(p->org, vpn) >= minparticledist || p->orientation == PARTICLE_BEAM)
1973                         R_MeshQueue_AddTransparent(p->org, R_DrawParticleCallback, p, 0);
1974 #endif
1975 }
1976