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