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