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