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