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