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