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