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