]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
added description string to all cvars and commands
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #ifdef WORKINGLQUAKE
24 #define lhrandom(MIN,MAX) ((rand() & 32767) * (((MAX)-(MIN)) * (1.0f / 32767.0f)) + (MIN))
25 #define NUMVERTEXNORMALS        162
26 siextern float r_avertexnormals[NUMVERTEXNORMALS][3];
27 #define m_bytenormals r_avertexnormals
28 #define 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, cl.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, (1.0f / cl_particles_quality.value) * 32 * (8 - r), (1.0f / cl_particles_quality.value) * 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, (1.0f / cl_particles_quality.value) * 32 * (8 - r), (1.0f / cl_particles_quality.value) * 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, (1.0f / cl_particles_quality.value) * lhrandom(128, 255), (1.0f / cl_particles_quality.value) * 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, (1.0f / cl_particles_quality.value) * lhrandom(0, 255), (1.0f / cl_particles_quality.value) * 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, (1.0f / cl_particles_quality.value) * 255, (1.0f/cl_particles_quality.value)*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), (1.0f / cl_particles_quality.value) * 255, (1.0f/cl_particles_quality.value)*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, (1.0f / cl_particles_quality.value) * lhrandom(182, 255), (1.0f/cl_particles_quality.value)*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, (1.0f / cl_particles_quality.value) * lhrandom(182, 255), (1.0f/cl_particles_quality.value)*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, (1.0f / cl_particles_quality.value) * lhrandom(51, 255), (1.0f / cl_particles_quality.value) * 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, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 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, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 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)
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, (1.0f / cl_particles_quality.value) * lhrandom(64, 255), (1.0f / cl_particles_quality.value) * 512, gravityscale, 0, org[0], org[1], org[2], dir[0], dir[1], dir[2] + sv_gravity.value * 0.1, 0, 0, 64);
832                 }
833         }
834 }
835
836 void CL_Smoke (vec3_t org, vec3_t dir, int count)
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, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 1024, 0, 0, trace.endpos[0], trace.endpos[1], trace.endpos[2], 0, 0, 0, 0, 0, 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;
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_quality.value, cl_particles_blood_alpha.value * 384 / cl_particles_quality.value, 0, -1, trace.endpos[0], trace.endpos[1], trace.endpos[2], vel[0], vel[1], vel[2], 1, 0, s);
900                 bloodcount -= 16 / cl_particles_quality.value;
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;
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 / cl_particles_quality.value;
929                 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, cl_particles_blood_alpha.value * 768 / cl_particles_quality.value, cl_particles_blood_alpha.value * 384 / cl_particles_quality.value, 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 / cl_particles_quality.value, (255 / cl_particles_quality.value) / 2, 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) / cl_particles_quality.value, 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) / cl_particles_quality.value, 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) / cl_particles_quality.value, 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) / cl_particles_quality.value, 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) / cl_particles_quality.value, 128 / cl_particles_quality.value, 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) / cl_particles_quality.value, 384 / cl_particles_quality.value, -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) / cl_particles_quality.value, 64 / cl_particles_quality.value, 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) / cl_particles_quality.value, 384 / cl_particles_quality.value, -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 = qd*3;
1233                                         r = rand()&3;
1234                                         color = particlepalette[ramp3[r]];
1235                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*42*(6-r), qd*306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
1236                                 }
1237                                 else
1238                                 {
1239                                         dec = qd*3;
1240                                         if (smoke)
1241                                         {
1242                                                 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, qd*cl_particles_smoke_alpha.value*62, qd*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, qd*cl_particles_smoke_alpha.value*288, qd*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, qd*lhrandom(64, 255), qd*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 = qd*3;
1254                                         r = 2 + (rand()%5);
1255                                         color = particlepalette[ramp3[r]];
1256                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*42*(6-r), qd*306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
1257                                 }
1258                                 else
1259                                 {
1260                                         dec = qd*3;
1261                                         if (smoke)
1262                                                 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, qd*cl_particles_smoke_alpha.value*50, qd*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 = qd*3;
1274                                                 color = particlepalette[67 + (rand()&3)];
1275                                                 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*255, qd*128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
1276                                         }
1277                                         else
1278                                         {
1279                                                 dec = qd*6;
1280                                                 color = particlepalette[67 + (rand()&3)];
1281                                                 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*255, qd*128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 3, 0);
1282                                         }
1283                                 }
1284                                 else
1285                                 {
1286                                         dec = qd*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 = qd*6;
1296                                         color = particlepalette[52 + (rand()&7)];
1297                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*255, qd*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, qd*255, qd*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 = qd*16;
1303                                         if (smoke)
1304                                         {
1305                                                 if (gamemode == GAME_GOODVSBAD2)
1306                                                 {
1307                                                         dec = qd*6;
1308                                                         particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, qd*128, qd*384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
1309                                                 }
1310                                                 else
1311                                                 {
1312                                                         dec = qd*3;
1313                                                         color = particlepalette[20 + (rand()&7)];
1314                                                         particle(particletype + pt_static, color, color, tex_particle, 2, qd*64, qd*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 = qd*6;
1324                                         color = particlepalette[230 + (rand()&7)];
1325                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*255, qd*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, qd*255, qd*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 = qd*3;
1331                                         if (smoke)
1332                                         {
1333                                                 color = particlepalette[226 + (rand()&7)];
1334                                                 particle(particletype + pt_static, color, color, tex_particle, 2, qd*64, qd*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 = qd*3;
1343                                         color = particlepalette[152 + (rand()&3)];
1344                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 1, qd*255, qd*850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 8, 0);
1345                                 }
1346                                 else
1347                                 {
1348                                         dec = qd*16;
1349                                         if (smoke)
1350                                         {
1351                                                 if (gamemode == GAME_GOODVSBAD2)
1352                                                 {
1353                                                         dec = qd*6;
1354                                                         particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, qd*255, qd*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 = qd*6;
1359                                                         particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, qd*64, qd*192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0);
1360                                                 }
1361                                                 else
1362                                                 {
1363                                                         dec = qd*3;
1364                                                         particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, qd*64, qd*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 = qd*7;
1372                                 if (smoke)
1373                                         particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, qd*64, qd*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 = qd*4;
1377                                 if (smoke)
1378                                         particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, qd*255, qd*1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 16);
1379                                 break;
1380                         case 9: // glow trail
1381                                 dec = qd*3;
1382                                 if (smoke)
1383                                         particle(particletype + pt_alphastatic, color, color, tex_particle, 5, qd*128, qd*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                 len -= dec;
1392                 VectorMA (pos, dec, vec, pos);
1393         }
1394 #ifndef WORKINGLQUAKE
1395         ent->persistent.trail_time = len;
1396 #endif
1397 }
1398
1399 void CL_BeamParticle (const vec3_t start, const vec3_t end, vec_t radius, float red, float green, float blue, float alpha, float lifetime)
1400 {
1401         int tempcolor2, cr, cg, cb;
1402         cr = red * 255;
1403         cg = green * 255;
1404         cb = blue * 255;
1405         tempcolor2 = (bound(0, cr, 255) << 16) | (bound(0, cg, 255) << 8) | bound(0, cb, 255);
1406         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);
1407 }
1408
1409 void CL_Tei_Smoke(const vec3_t org, const vec3_t dir, int count)
1410 {
1411         float f;
1412         if (!cl_particles.integer) return;
1413
1414         // smoke puff
1415         if (cl_particles_smoke.integer)
1416                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1417                         particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 255 / cl_particles_quality.value, 512 / cl_particles_quality.value, 0, 0, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, count * 0.125f, count * 0.5f);
1418 }
1419
1420 void CL_Tei_PlasmaHit(const vec3_t org, const vec3_t dir, int count)
1421 {
1422         float f;
1423         if (!cl_particles.integer) return;
1424
1425         if (cl_stainmaps.integer)
1426                 R_Stain(org, 40, 96, 96, 96, 40, 128, 128, 128, 40);
1427         CL_SpawnDecalParticleForPoint(org, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1428
1429         // smoke puff
1430         if (cl_particles_smoke.integer)
1431                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1432                         particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 255 / cl_particles_quality.value, 512 / cl_particles_quality.value, 0, 0, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, count * 0.125f, count);
1433
1434         // sparks
1435         if (cl_particles_sparks.integer)
1436                 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1437                         particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, lhrandom(64, 255) / cl_particles_quality.value, 512 / cl_particles_quality.value, 0, 0, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, count * 3.0f);
1438 }
1439
1440 /*
1441 ===============
1442 CL_MoveParticles
1443 ===============
1444 */
1445 void CL_MoveParticles (void)
1446 {
1447         particle_t *p;
1448         int i, maxparticle, j, a, content;
1449         float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1450         int hitent;
1451         trace_t trace;
1452
1453         // LordHavoc: early out condition
1454         if (!cl_numparticles)
1455         {
1456                 cl_freeparticle = 0;
1457                 return;
1458         }
1459
1460 #ifdef WORKINGLQUAKE
1461         frametime = cl.frametime;
1462 #else
1463         frametime = cl.time - cl.oldtime;
1464 #endif
1465         gravity = frametime * sv_gravity.value;
1466         dvel = 1+4*frametime;
1467         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1468
1469         maxparticle = -1;
1470         j = 0;
1471         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
1472         {
1473                 if (!p->type)
1474                         continue;
1475                 maxparticle = i;
1476                 content = 0;
1477
1478                 p->alpha -= p->alphafade * frametime;
1479
1480                 if (p->alpha <= 0)
1481                 {
1482                         p->type = NULL;
1483                         continue;
1484                 }
1485
1486                 if (p->type->orientation != PARTICLE_BEAM)
1487                 {
1488                         VectorCopy(p->org, oldorg);
1489                         VectorMA(p->org, frametime, p->vel, p->org);
1490                         VectorCopy(p->org, org);
1491                         if (p->bounce)
1492                         {
1493                                 if (p->type == particletype + pt_rain)
1494                                 {
1495                                         // raindrop - splash on solid/water/slime/lava
1496                                         trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, false);
1497                                         if (trace.fraction < 1)
1498                                         {
1499                                                 int count;
1500                                                 // convert from a raindrop particle to a rainsplash decal
1501                                                 VectorCopy(trace.endpos, p->org);
1502                                                 VectorCopy(trace.plane.normal, p->vel);
1503                                                 VectorAdd(p->org, p->vel, p->org);
1504                                                 p->type = particletype + pt_raindecal;
1505                                                 p->texnum = tex_rainsplash[0];
1506                                                 p->time2 = cl.time;
1507                                                 p->alphafade = p->alpha / 0.4;
1508                                                 p->bounce = 0;
1509                                                 p->friction = 0;
1510                                                 p->gravity = 0;
1511                                                 p->size = 8.0;
1512                                                 count = rand() & 3;
1513                                                 while(count--)
1514                                                         particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, (1.0f / cl_particles_quality.value) * lhrandom(64, 255), (1.0f / cl_particles_quality.value) * 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);
1515                                         }
1516                                 }
1517                                 else if (p->type == particletype + pt_blood)
1518                                 {
1519                                         // blood - splash on solid
1520                                         trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID, false);
1521                                         if (trace.fraction < 1)
1522                                         {
1523                                                 // convert from a blood particle to a blood decal
1524                                                 VectorCopy(trace.endpos, p->org);
1525                                                 VectorCopy(trace.plane.normal, p->vel);
1526                                                 VectorAdd(p->org, p->vel, p->org);
1527 #ifndef WORKINGLQUAKE
1528                                                 if (cl_stainmaps.integer)
1529                                                         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));
1530 #endif
1531                                                 if (!cl_decals.integer)
1532                                                 {
1533                                                         p->type = NULL;
1534                                                         continue;
1535                                                 }
1536
1537                                                 p->type = particletype + pt_decal;
1538                                                 p->texnum = tex_blooddecal[rand()&7];
1539 #ifndef WORKINGLQUAKE
1540                                                 p->owner = hitent;
1541                                                 p->ownermodel = cl_entities[hitent].render.model;
1542                                                 Matrix4x4_Transform(&cl_entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1543                                                 Matrix4x4_Transform3x3(&cl_entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1544 #endif
1545                                                 p->time2 = cl.time;
1546                                                 p->alphafade = 0;
1547                                                 p->bounce = 0;
1548                                                 p->friction = 0;
1549                                                 p->gravity = 0;
1550                                                 p->size *= 2.0f;
1551                                         }
1552                                 }
1553                                 else
1554                                 {
1555                                         trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, NULL, SUPERCONTENTS_SOLID, false);
1556                                         if (trace.fraction < 1)
1557                                         {
1558                                                 VectorCopy(trace.endpos, p->org);
1559                                                 if (p->bounce < 0)
1560                                                 {
1561                                                         p->type = NULL;
1562                                                         continue;
1563                                                 }
1564                                                 else
1565                                                 {
1566                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1567                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1568                                                         if (DotProduct(p->vel, p->vel) < 0.03)
1569                                                                 VectorClear(p->vel);
1570                                                 }
1571                                         }
1572                                 }
1573                         }
1574                         p->vel[2] -= p->gravity * gravity;
1575
1576                         if (p->friction)
1577                         {
1578                                 f = p->friction * frametime;
1579 #ifdef WORKINGLQUAKE
1580                                 if (CL_PointQ1Contents(p->org) != CONTENTS_EMPTY)
1581 #else
1582                                 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1583 #endif
1584                                         f *= 4;
1585                                 f = 1.0f - f;
1586                                 VectorScale(p->vel, f, p->vel);
1587                         }
1588                 }
1589
1590                 if (p->type != particletype + pt_static)
1591                 {
1592                         switch (p->type - particletype)
1593                         {
1594                         case pt_entityparticle:
1595                                 // particle that removes itself after one rendered frame
1596                                 if (p->time2)
1597                                         p->type = NULL;
1598                                 else
1599                                         p->time2 = 1;
1600                                 break;
1601                         case pt_blood:
1602 #ifdef WORKINGLQUAKE
1603                                 a = CL_PointQ1Contents(p->org);
1604                                 if (a <= CONTENTS_WATER)
1605 #else
1606                                 a = CL_PointSuperContents(p->org);
1607                                 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1608 #endif
1609                                 {
1610                                         p->size += frametime * 8;
1611                                         //p->alpha -= bloodwaterfade;
1612                                 }
1613                                 else
1614                                         p->vel[2] -= gravity;
1615 #ifdef WORKINGLQUAKE
1616                                 if (a == CONTENTS_SOLID || a == CONTENTS_LAVA)
1617 #else
1618                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1619 #endif
1620                                         p->type = NULL;
1621                                 break;
1622                         case pt_bubble:
1623 #ifdef WORKINGLQUAKE
1624                                 a = CL_PointQ1Contents(p->org);
1625                                 if (a != CONTENTS_WATER && a != CONTENTS_SLIME)
1626 #else
1627                                 a = CL_PointSuperContents(p->org);
1628                                 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1629 #endif
1630                                 {
1631                                         p->type = NULL;
1632                                         break;
1633                                 }
1634                                 break;
1635                         case pt_rain:
1636 #ifdef WORKINGLQUAKE
1637                                 a = CL_PointQ1Contents(p->org);
1638                                 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1639 #else
1640                                 a = CL_PointSuperContents(p->org);
1641                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
1642 #endif
1643                                         p->type = NULL;
1644                                 break;
1645                         case pt_snow:
1646                                 if (cl.time > p->time2)
1647                                 {
1648                                         // snow flutter
1649                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1650                                         p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1651                                         p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1652                                         //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1653                                 }
1654 #ifdef WORKINGLQUAKE
1655                                 a = CL_PointQ1Contents(p->org);
1656                                 if (a != CONTENTS_EMPTY && a != CONTENTS_SKY)
1657 #else
1658                                 a = CL_PointSuperContents(p->org);
1659                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
1660 #endif
1661                                         p->type = NULL;
1662                                 break;
1663                         case pt_smoke:
1664                                 //p->size += frametime * 15;
1665                                 break;
1666                         case pt_decal:
1667                                 // FIXME: this has fairly wacky handling of alpha
1668                                 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1669 #ifndef WORKINGLQUAKE
1670                                 if (cl_entities[p->owner].render.model == p->ownermodel)
1671                                 {
1672                                         Matrix4x4_Transform(&cl_entities[p->owner].render.matrix, p->relativeorigin, p->org);
1673                                         Matrix4x4_Transform3x3(&cl_entities[p->owner].render.matrix, p->relativedirection, p->vel);
1674                                 }
1675                                 else
1676                                         p->type = NULL;
1677 #endif
1678                                 break;
1679                         case pt_raindecal:
1680                                 a = max(0, (cl.time - p->time2) * 40);
1681                                 if (a < 16)
1682                                         p->texnum = tex_rainsplash[a];
1683                                 else
1684                                         p->type = NULL;
1685                                 break;
1686                         default:
1687                                 break;
1688                         }
1689                 }
1690         }
1691         cl_numparticles = maxparticle + 1;
1692         cl_freeparticle = 0;
1693 }
1694
1695 #define MAX_PARTICLETEXTURES 64
1696 // particletexture_t is a rectangle in the particlefonttexture
1697 typedef struct particletexture_s
1698 {
1699         rtexture_t *texture;
1700         float s1, t1, s2, t2;
1701 }
1702 particletexture_t;
1703
1704 #if WORKINGLQUAKE
1705 static int particlefonttexture;
1706 #else
1707 static rtexturepool_t *particletexturepool;
1708 static rtexture_t *particlefonttexture;
1709 #endif
1710 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1711
1712 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1713
1714 #define PARTICLETEXTURESIZE 64
1715 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1716
1717 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1718 {
1719         float dz, f, dot;
1720         vec3_t normal;
1721         dz = 1 - (dx*dx+dy*dy);
1722         if (dz > 0) // it does hit the sphere
1723         {
1724                 f = 0;
1725                 // back side
1726                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1727                 VectorNormalize(normal);
1728                 dot = DotProduct(normal, light);
1729                 if (dot > 0.5) // interior reflection
1730                         f += ((dot *  2) - 1);
1731                 else if (dot < -0.5) // exterior reflection
1732                         f += ((dot * -2) - 1);
1733                 // front side
1734                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1735                 VectorNormalize(normal);
1736                 dot = DotProduct(normal, light);
1737                 if (dot > 0.5) // interior reflection
1738                         f += ((dot *  2) - 1);
1739                 else if (dot < -0.5) // exterior reflection
1740                         f += ((dot * -2) - 1);
1741                 f *= 128;
1742                 f += 16; // just to give it a haze so you can see the outline
1743                 f = bound(0, f, 255);
1744                 return (unsigned char) f;
1745         }
1746         else
1747                 return 0;
1748 }
1749
1750 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1751 {
1752         int basex, basey, y;
1753         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1754         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1755         particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1756         particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1757         particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1758         particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1759         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1760                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1761 }
1762
1763 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1764 {
1765         int x, y;
1766         float cx, cy, dx, dy, f, iradius;
1767         unsigned char *d;
1768         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1769         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1770         iradius = 1.0f / radius;
1771         alpha *= (1.0f / 255.0f);
1772         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1773         {
1774                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1775                 {
1776                         dx = (x - cx);
1777                         dy = (y - cy);
1778                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1779                         if (f > 0)
1780                         {
1781                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1782                                 d[0] += f * (red   - d[0]);
1783                                 d[1] += f * (green - d[1]);
1784                                 d[2] += f * (blue  - d[2]);
1785                         }
1786                 }
1787         }
1788 }
1789
1790 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1791 {
1792         int i;
1793         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1794         {
1795                 data[0] = bound(minr, data[0], maxr);
1796                 data[1] = bound(ming, data[1], maxg);
1797                 data[2] = bound(minb, data[2], maxb);
1798         }
1799 }
1800
1801 void particletextureinvert(unsigned char *data)
1802 {
1803         int i;
1804         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1805         {
1806                 data[0] = 255 - data[0];
1807                 data[1] = 255 - data[1];
1808                 data[2] = 255 - data[2];
1809         }
1810 }
1811
1812 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1813 static void R_InitBloodTextures (unsigned char *particletexturedata)
1814 {
1815         int i, j, k, m;
1816         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1817
1818         // blood particles
1819         for (i = 0;i < 8;i++)
1820         {
1821                 memset(&data[0][0][0], 255, sizeof(data));
1822                 for (k = 0;k < 24;k++)
1823                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1824                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1825                 particletextureinvert(&data[0][0][0]);
1826                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1827         }
1828
1829         // blood decals
1830         for (i = 0;i < 8;i++)
1831         {
1832                 memset(&data[0][0][0], 255, sizeof(data));
1833                 m = 8;
1834                 for (j = 1;j < 10;j++)
1835                         for (k = min(j, m - 1);k < m;k++)
1836                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1837                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1838                 particletextureinvert(&data[0][0][0]);
1839                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1840         }
1841
1842 }
1843
1844 static void R_InitParticleTexture (void)
1845 {
1846         int x, y, d, i, k, m;
1847         float dx, dy, radius, f, f2;
1848         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1849         vec3_t light;
1850         unsigned char *particletexturedata;
1851
1852         // a note: decals need to modulate (multiply) the background color to
1853         // properly darken it (stain), and they need to be able to alpha fade,
1854         // this is a very difficult challenge because it means fading to white
1855         // (no change to background) rather than black (darkening everything
1856         // behind the whole decal polygon), and to accomplish this the texture is
1857         // inverted (dark red blood on white background becomes brilliant cyan
1858         // and white on black background) so we can alpha fade it to black, then
1859         // we invert it again during the blendfunc to make it work...
1860
1861         particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1862         memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1863
1864         // smoke
1865         for (i = 0;i < 8;i++)
1866         {
1867                 memset(&data[0][0][0], 255, sizeof(data));
1868                 do
1869                 {
1870                         unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1871
1872                         fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1873                         fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1874                         m = 0;
1875                         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1876                         {
1877                                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1878                                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1879                                 {
1880                                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1881                                         d = (noise2[y][x] - 128) * 3 + 192;
1882                                         if (d > 0)
1883                                                 d = d * (1-(dx*dx+dy*dy));
1884                                         d = (d * noise1[y][x]) >> 7;
1885                                         d = bound(0, d, 255);
1886                                         data[y][x][3] = (unsigned char) d;
1887                                         if (m < d)
1888                                                 m = d;
1889                                 }
1890                         }
1891                 }
1892                 while (m < 224);
1893                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1894         }
1895
1896         // rain splash
1897         for (i = 0;i < 16;i++)
1898         {
1899                 memset(&data[0][0][0], 255, sizeof(data));
1900                 radius = i * 3.0f / 4.0f / 16.0f;
1901                 f2 = 255.0f * ((15.0f - i) / 15.0f);
1902                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1903                 {
1904                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1905                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1906                         {
1907                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1908                                 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1909                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1910                         }
1911                 }
1912                 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1913         }
1914
1915         // normal particle
1916         memset(&data[0][0][0], 255, sizeof(data));
1917         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1918         {
1919                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1920                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1921                 {
1922                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1923                         d = 256 * (1 - (dx*dx+dy*dy));
1924                         d = bound(0, d, 255);
1925                         data[y][x][3] = (unsigned char) d;
1926                 }
1927         }
1928         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1929
1930         // rain
1931         memset(&data[0][0][0], 255, sizeof(data));
1932         light[0] = 1;light[1] = 1;light[2] = 1;
1933         VectorNormalize(light);
1934         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1935         {
1936                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1937                 // stretch upper half of bubble by +50% and shrink lower half by -50%
1938                 // (this gives an elongated teardrop shape)
1939                 if (dy > 0.5f)
1940                         dy = (dy - 0.5f) * 2.0f;
1941                 else
1942                         dy = (dy - 0.5f) / 1.5f;
1943                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1944                 {
1945                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1946                         // shrink bubble width to half
1947                         dx *= 2.0f;
1948                         data[y][x][3] = shadebubble(dx, dy, light);
1949                 }
1950         }
1951         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1952
1953         // bubble
1954         memset(&data[0][0][0], 255, sizeof(data));
1955         light[0] = 1;light[1] = 1;light[2] = 1;
1956         VectorNormalize(light);
1957         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1958         {
1959                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1960                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1961                 {
1962                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1963                         data[y][x][3] = shadebubble(dx, dy, light);
1964                 }
1965         }
1966         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1967
1968         // Blood particles and blood decals
1969         R_InitBloodTextures (particletexturedata);
1970
1971         // bullet decals
1972         for (i = 0;i < 8;i++)
1973         {
1974                 memset(&data[0][0][0], 255, sizeof(data));
1975                 for (k = 0;k < 12;k++)
1976                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1977                 for (k = 0;k < 3;k++)
1978                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1979                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1980                 particletextureinvert(&data[0][0][0]);
1981                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1982         }
1983
1984 #if WORKINGLQUAKE
1985         glBindTexture(GL_TEXTURE_2D, (particlefonttexture = gl_extension_number++));
1986         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1987         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1988 #else
1989
1990 #if 0
1991         Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1992 #endif
1993
1994         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1995         if (!particlefonttexture)
1996                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1997         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1998                 particletexture[i].texture = particlefonttexture;
1999
2000         // nexbeam
2001         fractalnoise(&noise3[0][0], 64, 4);
2002         m = 0;
2003         for (y = 0;y < 64;y++)
2004         {
2005                 dy = (y - 0.5f*64) / (64*0.5f-1);
2006                 for (x = 0;x < 16;x++)
2007                 {
2008                         dx = (x - 0.5f*16) / (16*0.5f-2);
2009                         d = (1 - sqrt(fabs(dx))) * noise3[y][x];
2010                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2011                         data2[y][x][3] = 255;
2012                 }
2013         }
2014
2015 #if 0
2016         Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2017 #endif
2018
2019         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2020         if (!particletexture[tex_beam].texture)
2021                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2022         particletexture[tex_beam].s1 = 0;
2023         particletexture[tex_beam].t1 = 0;
2024         particletexture[tex_beam].s2 = 1;
2025         particletexture[tex_beam].t2 = 1;
2026 #endif
2027         Mem_Free(particletexturedata);
2028 }
2029
2030 static void r_part_start(void)
2031 {
2032         particletexturepool = R_AllocTexturePool();
2033         R_InitParticleTexture ();
2034 }
2035
2036 static void r_part_shutdown(void)
2037 {
2038         R_FreeTexturePool(&particletexturepool);
2039 }
2040
2041 static void r_part_newmap(void)
2042 {
2043         cl_numparticles = 0;
2044         cl_freeparticle = 0;
2045 }
2046
2047 void R_Particles_Init (void)
2048 {
2049         Cvar_RegisterVariable(&r_drawparticles);
2050 #ifdef WORKINGLQUAKE
2051         r_part_start();
2052 #else
2053         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2054 #endif
2055 }
2056
2057 #ifdef WORKINGLQUAKE
2058 void R_InitParticles(void)
2059 {
2060         CL_Particles_Init();
2061         R_Particles_Init();
2062 }
2063 #endif
2064
2065 float particle_vertex3f[12], particle_texcoord2f[8];
2066
2067 #ifdef WORKINGLQUAKE
2068 void R_DrawParticle(particle_t *p)
2069 {
2070 #else
2071 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
2072 {
2073         const particle_t *p = particles + surfacenumber;
2074         rmeshstate_t m;
2075 #endif
2076         pblend_t blendmode;
2077         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2078         particletexture_t *tex;
2079
2080         VectorCopy(p->org, org);
2081
2082         blendmode = p->type->blendmode;
2083         tex = &particletexture[p->texnum];
2084         cr = p->color[0] * (1.0f / 255.0f);
2085         cg = p->color[1] * (1.0f / 255.0f);
2086         cb = p->color[2] * (1.0f / 255.0f);
2087         ca = p->alpha * (1.0f / 255.0f);
2088         if (blendmode == PBLEND_MOD)
2089         {
2090                 cr *= ca;
2091                 cg *= ca;
2092                 cb *= ca;
2093                 cr = min(cr, 1);
2094                 cg = min(cg, 1);
2095                 cb = min(cb, 1);
2096                 ca = 1;
2097         }
2098 #ifndef WORKINGLQUAKE
2099         if (p->type->lighting)
2100         {
2101                 float ambient[3], diffuse[3], diffusenormal[3];
2102                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
2103                 cr *= (ambient[0] + 0.5 * diffuse[0]);
2104                 cg *= (ambient[1] + 0.5 * diffuse[1]);
2105                 cb *= (ambient[2] + 0.5 * diffuse[2]);
2106         }
2107         if (fogenabled)
2108         {
2109                 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
2110                 ifog = 1 - fog;
2111                 cr = cr * ifog;
2112                 cg = cg * ifog;
2113                 cb = cb * ifog;
2114                 if (blendmode == PBLEND_ALPHA)
2115                 {
2116                         cr += fogcolor[0] * fog;
2117                         cg += fogcolor[1] * fog;
2118                         cb += fogcolor[2] * fog;
2119                 }
2120         }
2121
2122         R_Mesh_Matrix(&r_identitymatrix);
2123
2124         memset(&m, 0, sizeof(m));
2125         m.tex[0] = R_GetTexture(tex->texture);
2126         m.pointer_texcoord[0] = particle_texcoord2f;
2127         m.pointer_vertex = particle_vertex3f;
2128         R_Mesh_State(&m);
2129
2130         GL_Color(cr, cg, cb, ca);
2131
2132         if (blendmode == PBLEND_ALPHA)
2133                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2134         else if (blendmode == PBLEND_ADD)
2135                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2136         else //if (blendmode == PBLEND_MOD)
2137                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2138         GL_DepthMask(false);
2139         GL_DepthTest(true);
2140 #endif
2141         size = p->size * cl_particles_size.value;
2142         if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2143         {
2144                 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2145                 {
2146                         // double-sided
2147                         if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
2148                         {
2149                                 VectorNegate(p->vel, v);
2150                                 VectorVectors(v, right, up);
2151                         }
2152                         else
2153                                 VectorVectors(p->vel, right, up);
2154                         VectorScale(right, size, right);
2155                         VectorScale(up, size, up);
2156                 }
2157                 else
2158                 {
2159                         VectorScale(r_viewleft, -size, right);
2160                         VectorScale(r_viewup, size, up);
2161                 }
2162                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
2163                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
2164                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
2165                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
2166                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
2167                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
2168                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
2169                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
2170                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
2171                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
2172                 particle_vertex3f[10] = org[1] + right[1] - up[1];
2173                 particle_vertex3f[11] = org[2] + right[2] - up[2];
2174                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2175                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2176                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2177                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2178         }
2179         else if (p->type->orientation == PARTICLE_SPARK)
2180         {
2181                 VectorMA(p->org, -0.02, p->vel, v);
2182                 VectorMA(p->org, 0.02, p->vel, up2);
2183                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
2184                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
2185                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
2186                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
2187                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
2188         }
2189         else if (p->type->orientation == PARTICLE_BEAM)
2190         {
2191                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
2192                 VectorSubtract(p->vel, p->org, up);
2193                 VectorNormalize(up);
2194                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2195                 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2196                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
2197                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
2198                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
2199                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
2200         }
2201         else
2202         {
2203                 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2204                 return;
2205         }
2206
2207 #if WORKINGLQUAKE
2208         if (blendmode == PBLEND_ALPHA)
2209                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2210         else if (blendmode == PBLEND_ADD)
2211                 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
2212         else //if (blendmode == PBLEND_MOD)
2213                 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2214         glColor4f(cr, cg, cb, ca);
2215         glBegin(GL_QUADS);
2216         glTexCoord2f(particle_texcoord2f[0], particle_texcoord2f[1]);glVertex3f(particle_vertex3f[ 0], particle_vertex3f[ 1], particle_vertex3f[ 2]);
2217         glTexCoord2f(particle_texcoord2f[2], particle_texcoord2f[3]);glVertex3f(particle_vertex3f[ 3], particle_vertex3f[ 4], particle_vertex3f[ 5]);
2218         glTexCoord2f(particle_texcoord2f[4], particle_texcoord2f[5]);glVertex3f(particle_vertex3f[ 6], particle_vertex3f[ 7], particle_vertex3f[ 8]);
2219         glTexCoord2f(particle_texcoord2f[6], particle_texcoord2f[7]);glVertex3f(particle_vertex3f[ 9], particle_vertex3f[10], particle_vertex3f[11]);
2220         glEnd();
2221 #else
2222         R_Mesh_Draw(0, 4, 2, polygonelements);
2223 #endif
2224 }
2225
2226 void R_DrawParticles (void)
2227 {
2228         int i;
2229         float minparticledist;
2230         particle_t *p;
2231
2232 #ifdef WORKINGLQUAKE
2233         CL_MoveParticles();
2234 #endif
2235
2236         // LordHavoc: early out conditions
2237         if ((!cl_numparticles) || (!r_drawparticles.integer))
2238                 return;
2239
2240         minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
2241
2242 #ifdef WORKINGLQUAKE
2243         glBindTexture(GL_TEXTURE_2D, particlefonttexture);
2244         glEnable(GL_BLEND);
2245         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
2246         glDepthMask(0);
2247         // LordHavoc: only render if not too close
2248         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
2249                 if (p->type && DotProduct(p->org, r_viewforward) >= minparticledist)
2250                         R_DrawParticle(p);
2251         glDepthMask(1);
2252         glDisable(GL_BLEND);
2253         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2254 #else
2255         // LordHavoc: only render if not too close
2256         for (i = 0, p = particles;i < cl_numparticles;i++, p++)
2257         {
2258                 if (p->type)
2259                 {
2260                         renderstats.particles++;
2261                         if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2262                         {
2263                                 if (p->type == particletype + pt_decal)
2264                                         R_DrawParticle_TransparentCallback(0, i, 0);
2265                                 else
2266                                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2267                         }
2268                 }
2269         }
2270 #endif
2271 }
2272