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