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