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