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