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