]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
fix bugs with bbox vs bbox traces (the collision box's planes didn't have correct...
[xonotic/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #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 | SUPERCONTENTS_SKY, false);
235                 // take the closest trace result that doesn't end up hitting a NOMARKS
236                 // surface (sky for example)
237                 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
238                 {
239                         bestfrac = trace.fraction;
240                         besthitent = hitent;
241                         VectorCopy(trace.endpos, bestorg);
242                         VectorCopy(trace.plane.normal, bestnormal);
243                 }
244         }
245         if (bestfrac < 1)
246                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
247 }
248
249 /*
250 ===============
251 CL_EntityParticles
252 ===============
253 */
254 void CL_EntityParticles (entity_t *ent)
255 {
256         int i;
257         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
258         static vec3_t avelocities[NUMVERTEXNORMALS];
259         if (!cl_particles.integer) return;
260
261         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
262
263         if (!avelocities[0][0])
264                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
265                         avelocities[0][i] = lhrandom(0, 2.55);
266
267         for (i = 0;i < NUMVERTEXNORMALS;i++)
268         {
269                 yaw = cl.time * avelocities[i][0];
270                 pitch = cl.time * avelocities[i][1];
271                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
272                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
273                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
274                 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);
275         }
276 }
277
278
279 void CL_ReadPointFile_f (void)
280 {
281         vec3_t org, leakorg;
282         int r, c, s;
283         char *pointfile = NULL, *pointfilepos, *t, tchar;
284         char name[MAX_OSPATH];
285
286         if (!cl.worldmodel)
287                 return;
288
289         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
290         strlcat (name, ".pts", sizeof (name));
291         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
292         if (!pointfile)
293         {
294                 Con_Printf("Could not open %s\n", name);
295                 return;
296         }
297
298         Con_Printf("Reading %s...\n", name);
299         VectorClear(leakorg);
300         c = 0;
301         s = 0;
302         pointfilepos = pointfile;
303         while (*pointfilepos)
304         {
305                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
306                         pointfilepos++;
307                 if (!*pointfilepos)
308                         break;
309                 t = pointfilepos;
310                 while (*t && *t != '\n' && *t != '\r')
311                         t++;
312                 tchar = *t;
313                 *t = 0;
314                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
315                 *t = tchar;
316                 pointfilepos = t;
317                 if (r != 3)
318                         break;
319                 if (c == 0)
320                         VectorCopy(org, leakorg);
321                 c++;
322
323                 if (cl.num_particles < cl.max_particles - 3)
324                 {
325                         s++;
326                         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);
327                 }
328         }
329         Mem_Free(pointfile);
330         VectorCopy(leakorg, org);
331         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
332
333         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);
334         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);
335         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);
336 }
337
338 /*
339 ===============
340 CL_ParseParticleEffect
341
342 Parse an effect out of the server message
343 ===============
344 */
345 void CL_ParseParticleEffect (void)
346 {
347         vec3_t org, dir;
348         int i, count, msgcount, color;
349
350         MSG_ReadVector(org, cls.protocol);
351         for (i=0 ; i<3 ; i++)
352                 dir[i] = MSG_ReadChar ();
353         msgcount = MSG_ReadByte ();
354         color = MSG_ReadByte ();
355
356         if (msgcount == 255)
357                 count = 1024;
358         else
359                 count = msgcount;
360
361         if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer)
362         {
363                 if (color == 73)
364                 {
365                         // regular blood
366                         CL_BloodPuff(org, dir, count / 2);
367                         return;
368                 }
369                 if (color == 225)
370                 {
371                         // lightning blood
372                         CL_BloodPuff(org, dir, count / 2);
373                         return;
374                 }
375         }
376         CL_RunParticleEffect (org, dir, color, count);
377 }
378
379 /*
380 ===============
381 CL_ParticleExplosion
382
383 ===============
384 */
385 void CL_ParticleExplosion (vec3_t org)
386 {
387         int i;
388         trace_t trace;
389         //vec3_t v;
390         //vec3_t v2;
391         if (cl_stainmaps.integer)
392                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
393         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
394
395         if (cl_particles_quake.integer)
396         {
397                 for (i = 0;i < 1024;i++)
398                 {
399                         int r, color;
400                         r = rand()&3;
401                         if (i & 1)
402                         {
403                                 color = particlepalette[ramp1[r]];
404                                 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);
405                         }
406                         else
407                         {
408                                 color = particlepalette[ramp2[r]];
409                                 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);
410                         }
411                 }
412         }
413         else
414         {
415                 i = CL_PointSuperContents(org);
416                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
417                 {
418                         if (cl_particles.integer && cl_particles_bubbles.integer && cl_particles_explosions_bubbles.integer)
419                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
420                                         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);
421                 }
422                 else
423                 {
424                         // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
425                         // smoke puff
426                         if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
427                         {
428                                 for (i = 0;i < 32;i++)
429                                 {
430                                         int k;
431                                         vec3_t v, v2;
432                                         for (k = 0;k < 16;k++)
433                                         {
434                                                 v[0] = org[0] + lhrandom(-48, 48);
435                                                 v[1] = org[1] + lhrandom(-48, 48);
436                                                 v[2] = org[2] + lhrandom(-48, 48);
437                                                 trace = CL_TraceBox(org, vec3_origin, vec3_origin, v, true, NULL, SUPERCONTENTS_SOLID, false);
438                                                 if (trace.fraction >= 0.1)
439                                                         break;
440                                         }
441                                         VectorSubtract(trace.endpos, org, v2);
442                                         VectorScale(v2, 2.0f, v2);
443                                         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);
444                                 }
445                         }
446
447                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
448                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
449                                         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);
450                 }
451         }
452
453         if (cl_particles_explosions_shell.integer)
454                 R_NewExplosion(org);
455 }
456
457 /*
458 ===============
459 CL_ParticleExplosion2
460
461 ===============
462 */
463 void CL_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength)
464 {
465         int i, k;
466         if (!cl_particles.integer) return;
467
468         for (i = 0;i < 512 * cl_particles_quality.value;i++)
469         {
470                 k = particlepalette[colorStart + (i % colorLength)];
471                 if (cl_particles_quake.integer)
472                         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);
473                 else
474                         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);
475         }
476 }
477
478 /*
479 ===============
480 CL_BlobExplosion
481
482 ===============
483 */
484 void CL_BlobExplosion (vec3_t org)
485 {
486         int i, k;
487         if (!cl_particles.integer) return;
488
489         if (!cl_particles_quake.integer)
490         {
491                 CL_ParticleExplosion(org);
492                 return;
493         }
494
495         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
496         {
497                 if (i & 1)
498                 {
499                         k = particlepalette[66 + rand()%6];
500                         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);
501                 }
502                 else
503                 {
504                         k = particlepalette[150 + rand()%6];
505                         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);
506                 }
507         }
508 }
509
510 /*
511 ===============
512 CL_RunParticleEffect
513
514 ===============
515 */
516 void CL_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count)
517 {
518         int k;
519
520         if (count == 1024)
521         {
522                 CL_ParticleExplosion(org);
523                 return;
524         }
525         if (!cl_particles.integer) return;
526         if (cl_particles_quake.integer)
527         {
528                 count *= cl_particles_quality.value;
529                 while (count--)
530                 {
531                         k = particlepalette[color + (rand()&7)];
532                         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);
533                 }
534         }
535         else
536         {
537                 count *= cl_particles_quality.value;
538                 while (count--)
539                 {
540                         k = particlepalette[color + (rand()&7)];
541                         if (gamemode == GAME_GOODVSBAD2)
542                                 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);
543                         else
544                                 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);
545                 }
546         }
547 }
548
549 // LordHavoc: added this for spawning sparks/dust (which have strong gravity)
550 /*
551 ===============
552 CL_SparkShower
553 ===============
554 */
555 void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale, vec_t radius)
556 {
557         int k;
558
559         if (!cl_particles.integer) return;
560
561         if (cl_particles_sparks.integer)
562         {
563                 // sparks
564                 count *= cl_particles_quality.value;
565                 while(count--)
566                 {
567                         k = particlepalette[0x68 + (rand() & 7)];
568                         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);
569                 }
570         }
571 }
572
573 void CL_Smoke (vec3_t org, vec3_t dir, int count, vec_t radius)
574 {
575         vec3_t org2;
576         int k;
577         trace_t trace;
578
579         if (!cl_particles.integer) return;
580
581         // smoke puff
582         if (cl_particles_smoke.integer)
583         {
584                 k = count * 0.25 * cl_particles_quality.value;
585                 while(k--)
586                 {
587                         org2[0] = org[0] + 0.125f * lhrandom(-count, count);
588                         org2[1] = org[1] + 0.125f * lhrandom(-count, count);
589                         org2[2] = org[2] + 0.125f * lhrandom(-count, count);
590                         trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, NULL, SUPERCONTENTS_SOLID, false);
591                         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);
592                 }
593         }
594 }
595
596 void CL_BulletMark (vec3_t org)
597 {
598         if (cl_stainmaps.integer)
599                 R_Stain(org, 32, 96, 96, 96, 24, 128, 128, 128, 24);
600         CL_SpawnDecalParticleForPoint(org, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
601 }
602
603 void CL_PlasmaBurn (vec3_t org)
604 {
605         if (cl_stainmaps.integer)
606                 R_Stain(org, 48, 96, 96, 96, 32, 128, 128, 128, 32);
607         CL_SpawnDecalParticleForPoint(org, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
608 }
609
610 static float bloodcount = 0;
611 void CL_BloodPuff (vec3_t org, vec3_t vel, int count)
612 {
613         float s;
614         vec3_t org2;
615         trace_t trace;
616         // bloodcount is used to accumulate counts too small to cause a blood particle
617         if (!cl_particles.integer) return;
618         if (cl_particles_quake.integer)
619         {
620                 CL_RunParticleEffect(org, vel, 73, count * 2);
621                 return;
622         }
623         if (!cl_particles_blood.integer) return;
624
625         s = count + 64.0f;
626         count *= 5.0f;
627         if (count > 1000)
628                 count = 1000;
629         bloodcount += count * cl_particles_quality.value;
630         while(bloodcount > 0)
631         {
632                 org2[0] = org[0] + 0.125f * lhrandom(-bloodcount, bloodcount);
633                 org2[1] = org[1] + 0.125f * lhrandom(-bloodcount, bloodcount);
634                 org2[2] = org[2] + 0.125f * lhrandom(-bloodcount, bloodcount);
635                 trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, NULL, SUPERCONTENTS_SOLID, false);
636                 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);
637                 bloodcount -= 16;
638         }
639 }
640
641 void CL_BloodShower (vec3_t mins, vec3_t maxs, float velspeed, int count)
642 {
643         vec3_t org, vel, diff, center, velscale;
644         if (!cl_particles.integer) return;
645         if (!cl_particles_bloodshowers.integer) return;
646         if (!cl_particles_blood.integer) return;
647
648         VectorSubtract(maxs, mins, diff);
649         center[0] = (mins[0] + maxs[0]) * 0.5;
650         center[1] = (mins[1] + maxs[1]) * 0.5;
651         center[2] = (mins[2] + maxs[2]) * 0.5;
652         velscale[0] = velspeed * 2.0 / diff[0];
653         velscale[1] = velspeed * 2.0 / diff[1];
654         velscale[2] = velspeed * 2.0 / diff[2];
655
656         bloodcount += count * 5.0f * cl_particles_quality.value;
657         while (bloodcount > 0)
658         {
659                 org[0] = lhrandom(mins[0], maxs[0]);
660                 org[1] = lhrandom(mins[1], maxs[1]);
661                 org[2] = lhrandom(mins[2], maxs[2]);
662                 vel[0] = (org[0] - center[0]) * velscale[0];
663                 vel[1] = (org[1] - center[1]) * velscale[1];
664                 vel[2] = (org[2] - center[2]) * velscale[2];
665                 bloodcount -= 16;
666                 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);
667         }
668 }
669
670 void CL_ParticleCube (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int gravity, int randomvel)
671 {
672         int k;
673         float t;
674         if (!cl_particles.integer) return;
675         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
676         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
677         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
678
679         count *= cl_particles_quality.value;
680         while (count--)
681         {
682                 k = particlepalette[colorbase + (rand()&3)];
683                 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);
684         }
685 }
686
687 void CL_ParticleRain (vec3_t mins, vec3_t maxs, vec3_t dir, int count, int colorbase, int type)
688 {
689         int k;
690         float t, z, minz, maxz;
691         particle_t *p;
692         if (!cl_particles.integer) return;
693         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
694         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
695         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
696         if (dir[2] < 0) // falling
697                 z = maxs[2];
698         else // rising??
699                 z = mins[2];
700
701         minz = z - fabs(dir[2]) * 0.1;
702         maxz = z + fabs(dir[2]) * 0.1;
703         minz = bound(mins[2], minz, maxs[2]);
704         maxz = bound(mins[2], maxz, maxs[2]);
705
706         count *= cl_particles_quality.value;
707
708         switch(type)
709         {
710         case 0:
711                 count *= 4; // ick, this should be in the mod or maps?
712
713                 while(count--)
714                 {
715                         k = particlepalette[colorbase + (rand()&3)];
716                         if (gamemode == GAME_GOODVSBAD2)
717                                 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);
718                         else
719                                 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);
720                 }
721                 break;
722         case 1:
723                 while(count--)
724                 {
725                         k = particlepalette[colorbase + (rand()&3)];
726                         if (gamemode == GAME_GOODVSBAD2)
727                                 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);
728                         else
729                                 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);
730                         if (p)
731                                 VectorCopy(p->vel, p->relativedirection);
732                 }
733                 break;
734         default:
735                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
736         }
737 }
738
739 void CL_Stardust (vec3_t mins, vec3_t maxs, int count)
740 {
741         int k;
742         float t;
743         vec3_t o, v, center;
744         if (!cl_particles.integer) return;
745
746         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
747         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
748         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
749
750         center[0] = (mins[0] + maxs[0]) * 0.5f;
751         center[1] = (mins[1] + maxs[1]) * 0.5f;
752         center[2] = (mins[2] + maxs[2]) * 0.5f;
753
754         count *= cl_particles_quality.value;
755         while (count--)
756         {
757                 k = particlepalette[224 + (rand()&15)];
758                 o[0] = lhrandom(mins[0], maxs[0]);
759                 o[1] = lhrandom(mins[1], maxs[1]);
760                 o[2] = lhrandom(mins[2], maxs[2]);
761                 VectorSubtract(o, center, v);
762                 VectorNormalize(v);
763                 VectorScale(v, 100, v);
764                 v[2] += sv_gravity.value * 0.15f;
765                 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);
766         }
767 }
768
769 void CL_FlameCube (vec3_t mins, vec3_t maxs, int count)
770 {
771         int k;
772         float t;
773         if (!cl_particles.integer) return;
774         if (maxs[0] <= mins[0]) {t = mins[0];mins[0] = maxs[0];maxs[0] = t;}
775         if (maxs[1] <= mins[1]) {t = mins[1];mins[1] = maxs[1];maxs[1] = t;}
776         if (maxs[2] <= mins[2]) {t = mins[2];mins[2] = maxs[2];maxs[2] = t;}
777
778         count *= cl_particles_quality.value;
779         while (count--)
780         {
781                 k = particlepalette[224 + (rand()&15)];
782                 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);
783                 if (count & 1)
784                         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);
785         }
786 }
787
788 void CL_Flames (vec3_t org, vec3_t vel, int count)
789 {
790         int k;
791         if (!cl_particles.integer) return;
792
793         count *= cl_particles_quality.value;
794         while (count--)
795         {
796                 k = particlepalette[224 + (rand()&15)];
797                 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);
798         }
799 }
800
801
802
803 /*
804 ===============
805 CL_LavaSplash
806
807 ===============
808 */
809 void CL_LavaSplash (vec3_t origin)
810 {
811         float i, j, inc, vel;
812         int k, l;
813         vec3_t          dir, org;
814         if (!cl_particles.integer) return;
815
816         if (cl_particles_quake.integer)
817         {
818                 inc = 8 / cl_particles_quality.value;
819                 for (i = -128;i < 128;i += inc)
820                 {
821                         for (j = -128;j < 128;j += inc)
822                         {
823                                 dir[0] = j + lhrandom(0, inc);
824                                 dir[1] = i + lhrandom(0, inc);
825                                 dir[2] = 256;
826                                 org[0] = origin[0] + dir[0];
827                                 org[1] = origin[1] + dir[1];
828                                 org[2] = origin[2] + lhrandom(0, 64);
829                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
830                                 k = l = particlepalette[224 + (rand()&7)];
831                                 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);
832                         }
833                 }
834         }
835         else
836         {
837                 inc = 32 / cl_particles_quality.value;
838                 for (i = -128;i < 128;i += inc)
839                 {
840                         for (j = -128;j < 128;j += inc)
841                         {
842                                 dir[0] = j + lhrandom(0, inc);
843                                 dir[1] = i + lhrandom(0, inc);
844                                 dir[2] = 256;
845                                 org[0] = origin[0] + dir[0];
846                                 org[1] = origin[1] + dir[1];
847                                 org[2] = origin[2] + lhrandom(0, 64);
848                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
849                                 if (gamemode == GAME_GOODVSBAD2)
850                                 {
851                                         k = particlepalette[0 + (rand()&255)];
852                                         l = particlepalette[0 + (rand()&255)];
853                                         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);
854                                 }
855                                 else
856                                 {
857                                         k = l = particlepalette[224 + (rand()&7)];
858                                         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);
859                                 }
860                         }
861                 }
862         }
863 }
864
865 /*
866 ===============
867 CL_TeleportSplash
868
869 ===============
870 */
871 void CL_TeleportSplash (vec3_t org)
872 {
873         float i, j, k, inc;
874         if (!cl_particles.integer) return;
875
876         if (cl_particles_quake.integer)
877         {
878                 inc = 4 / cl_particles_quality.value;
879                 for (i = -16;i < 16;i += inc)
880                 {
881                         for (j = -16;j < 16;j += inc)
882                         {
883                                 for (k = -24;k < 32;k += inc)
884                                 {
885                                         vec3_t dir;
886                                         float vel;
887                                         VectorSet(dir, i*8, j*8, k*8);
888                                         VectorNormalize(dir);
889                                         vel = lhrandom(50, 113);
890                                         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);
891                                 }
892                         }
893                 }
894         }
895         else
896         {
897                 inc = 8 / cl_particles_quality.value;
898                 for (i = -16;i < 16;i += inc)
899                         for (j = -16;j < 16;j += inc)
900                                 for (k = -24;k < 32;k += inc)
901                                         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);
902         }
903 }
904
905 void CL_RocketTrail (vec3_t start, vec3_t end, int type, int color, entity_t *ent)
906 {
907         vec3_t vec, dir, vel, pos;
908         float len, dec, speed, qd;
909         int smoke, blood, bubbles, r;
910
911         if (end[0] == start[0] && end[1] == start[1] && end[2] == start[2])
912                 return;
913
914         VectorSubtract(end, start, dir);
915         VectorNormalize(dir);
916
917         VectorSubtract (end, start, vec);
918         len = VectorNormalizeLength (vec);
919         dec = -ent->persistent.trail_time;
920         ent->persistent.trail_time += len;
921         if (ent->persistent.trail_time < 0.01f)
922                 return;
923
924         // if we skip out, leave it reset
925         ent->persistent.trail_time = 0.0f;
926
927         speed = ent->state_current.time - ent->state_previous.time;
928         if (speed)
929                 speed = 1.0f / speed;
930         VectorSubtract(ent->state_current.origin, ent->state_previous.origin, vel);
931         color = particlepalette[color];
932         VectorScale(vel, speed, vel);
933
934         // advance into this frame to reach the first puff location
935         VectorMA(start, dec, vec, pos);
936         len -= dec;
937
938         smoke = cl_particles.integer && cl_particles_smoke.integer;
939         blood = cl_particles.integer && cl_particles_blood.integer;
940         bubbles = cl_particles.integer && cl_particles_bubbles.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
941         qd = 1.0f / cl_particles_quality.value;
942
943         while (len >= 0)
944         {
945                 switch (type)
946                 {
947                         case 0: // rocket trail
948                                 if (cl_particles_quake.integer)
949                                 {
950                                         dec = 3;
951                                         r = rand()&3;
952                                         color = particlepalette[ramp3[r]];
953                                         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);
954                                 }
955                                 else
956                                 {
957                                         dec = 3;
958                                         if (smoke)
959                                         {
960                                                 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);
961                                                 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);
962                                         }
963                                         if (bubbles)
964                                                 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);
965                                 }
966                                 break;
967
968                         case 1: // grenade trail
969                                 if (cl_particles_quake.integer)
970                                 {
971                                         dec = 3;
972                                         r = 2 + (rand()%5);
973                                         color = particlepalette[ramp3[r]];
974                                         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);
975                                 }
976                                 else
977                                 {
978                                         dec = 3;
979                                         if (smoke)
980                                                 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);
981                                 }
982                                 break;
983
984
985                         case 2: // blood
986                         case 4: // slight blood
987                                 if (cl_particles_quake.integer)
988                                 {
989                                         if (type == 2)
990                                         {
991                                                 dec = 3;
992                                                 color = particlepalette[67 + (rand()&3)];
993                                                 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);
994                                         }
995                                         else
996                                         {
997                                                 dec = 6;
998                                                 color = particlepalette[67 + (rand()&3)];
999                                                 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);
1000                                         }
1001                                 }
1002                                 else
1003                                 {
1004                                         dec = 16;
1005                                         if (blood)
1006                                                 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);
1007                                 }
1008                                 break;
1009
1010                         case 3: // green tracer
1011                                 if (cl_particles_quake.integer)
1012                                 {
1013                                         dec = 6;
1014                                         color = particlepalette[52 + (rand()&7)];
1015                                         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);
1016                                         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);
1017                                 }
1018                                 else
1019                                 {
1020                                         dec = 16;
1021                                         if (smoke)
1022                                         {
1023                                                 if (gamemode == GAME_GOODVSBAD2)
1024                                                 {
1025                                                         dec = 6;
1026                                                         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);
1027                                                 }
1028                                                 else
1029                                                 {
1030                                                         dec = 3;
1031                                                         color = particlepalette[20 + (rand()&7)];
1032                                                         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);
1033                                                 }
1034                                         }
1035                                 }
1036                                 break;
1037
1038                         case 5: // flame tracer
1039                                 if (cl_particles_quake.integer)
1040                                 {
1041                                         dec = 6;
1042                                         color = particlepalette[230 + (rand()&7)];
1043                                         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);
1044                                         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);
1045                                 }
1046                                 else
1047                                 {
1048                                         dec = 3;
1049                                         if (smoke)
1050                                         {
1051                                                 color = particlepalette[226 + (rand()&7)];
1052                                                 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);
1053                                         }
1054                                 }
1055                                 break;
1056
1057                         case 6: // voor trail
1058                                 if (cl_particles_quake.integer)
1059                                 {
1060                                         dec = 3;
1061                                         color = particlepalette[152 + (rand()&3)];
1062                                         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);
1063                                 }
1064                                 else
1065                                 {
1066                                         dec = 16;
1067                                         if (smoke)
1068                                         {
1069                                                 if (gamemode == GAME_GOODVSBAD2)
1070                                                 {
1071                                                         dec = 6;
1072                                                         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);
1073                                                 }
1074                                                 else if (gamemode == GAME_PRYDON)
1075                                                 {
1076                                                         dec = 6;
1077                                                         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);
1078                                                 }
1079                                                 else
1080                                                 {
1081                                                         dec = 3;
1082                                                         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);
1083                                                 }
1084                                         }
1085                                 }
1086                                 break;
1087                         case 7: // Nehahra smoke tracer
1088                                 dec = 7;
1089                                 if (smoke)
1090                                         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);
1091                                 break;
1092                         case 8: // Nexuiz plasma trail
1093                                 dec = 4;
1094                                 if (smoke)
1095                                         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);
1096                                 break;
1097                         case 9: // glow trail
1098                                 dec = 3;
1099                                 if (smoke)
1100                                         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);
1101                                 break;
1102                         default:
1103                                 Sys_Error("CL_RocketTrail: unknown trail type %i", type);
1104                 }
1105
1106                 // advance to next time and position
1107                 dec *= qd;
1108                 len -= dec;
1109                 VectorMA (pos, dec, vec, pos);
1110         }
1111         ent->persistent.trail_time = len;
1112 }
1113
1114 void CL_BeamParticle (const vec3_t start, const vec3_t end, vec_t radius, float red, float green, float blue, float alpha, float lifetime)
1115 {
1116         int tempcolor2, cr, cg, cb;
1117         cr = red * 255;
1118         cg = green * 255;
1119         cb = blue * 255;
1120         tempcolor2 = (bound(0, cr, 255) << 16) | (bound(0, cg, 255) << 8) | bound(0, cb, 255);
1121         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);
1122 }
1123
1124 void CL_Tei_Smoke(const vec3_t org, const vec3_t dir, int count)
1125 {
1126         float f;
1127         if (!cl_particles.integer) return;
1128
1129         // smoke puff
1130         if (cl_particles_smoke.integer)
1131                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1132                         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);
1133 }
1134
1135 void CL_Tei_PlasmaHit(const vec3_t org, const vec3_t dir, int count)
1136 {
1137         float f;
1138         if (!cl_particles.integer) return;
1139
1140         if (cl_stainmaps.integer)
1141                 R_Stain(org, 40, 96, 96, 96, 40, 128, 128, 128, 40);
1142         CL_SpawnDecalParticleForPoint(org, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1143
1144         // smoke puff
1145         if (cl_particles_smoke.integer)
1146                 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1147                         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);
1148
1149         // sparks
1150         if (cl_particles_sparks.integer)
1151                 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1152                         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);
1153 }
1154
1155 /*
1156 ===============
1157 CL_MoveParticles
1158 ===============
1159 */
1160 void CL_MoveParticles (void)
1161 {
1162         particle_t *p;
1163         int i, maxparticle, j, a, content;
1164         float gravity, dvel, bloodwaterfade, frametime, f, dist, org[3], oldorg[3];
1165         int hitent;
1166         trace_t trace;
1167
1168         // LordHavoc: early out condition
1169         if (!cl.num_particles)
1170         {
1171                 cl.free_particle = 0;
1172                 return;
1173         }
1174
1175         frametime = cl.time - cl.oldtime;
1176         gravity = frametime * sv_gravity.value;
1177         dvel = 1+4*frametime;
1178         bloodwaterfade = max(cl_particles_blood_alpha.value, 0.01f) * frametime * 128.0f;
1179
1180         maxparticle = -1;
1181         j = 0;
1182         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1183         {
1184                 if (!p->type)
1185                         continue;
1186                 maxparticle = i;
1187                 content = 0;
1188
1189                 p->alpha -= p->alphafade * frametime;
1190
1191                 if (p->alpha <= 0)
1192                 {
1193                         p->type = NULL;
1194                         continue;
1195                 }
1196
1197                 if (p->type->orientation != PARTICLE_BEAM)
1198                 {
1199                         VectorCopy(p->org, oldorg);
1200                         VectorMA(p->org, frametime, p->vel, p->org);
1201                         VectorCopy(p->org, org);
1202                         if (p->bounce)
1203                         {
1204                                 trace = CL_TraceBox(oldorg, vec3_origin, vec3_origin, p->org, true, &hitent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), false);
1205                                 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1206                                 // or if the trace hit something flagged as NOIMPACT
1207                                 // then remove the particle
1208                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1209                                 {
1210                                         p->type = NULL;
1211                                         continue;
1212                                 }
1213                                 // react if the particle hit something
1214                                 if (trace.fraction < 1)
1215                                 {
1216                                         VectorCopy(trace.endpos, p->org);
1217                                         if (p->type == particletype + pt_rain)
1218                                         {
1219                                                 // raindrop - splash on solid/water/slime/lava
1220                                                 int count;
1221                                                 // convert from a raindrop particle to a rainsplash decal
1222                                                 VectorCopy(trace.plane.normal, p->vel);
1223                                                 VectorAdd(p->org, p->vel, p->org);
1224                                                 p->type = particletype + pt_raindecal;
1225                                                 p->texnum = tex_rainsplash[0];
1226                                                 p->time2 = cl.time;
1227                                                 p->alphafade = p->alpha / 0.4;
1228                                                 p->bounce = 0;
1229                                                 p->friction = 0;
1230                                                 p->gravity = 0;
1231                                                 p->size = 8.0;
1232                                                 count = rand() & 3;
1233                                                 while(count--)
1234                                                         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);
1235                                         }
1236                                         else if (p->type == particletype + pt_blood)
1237                                         {
1238                                                 // blood - splash on solid
1239                                                 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1240                                                 {
1241                                                         p->type = NULL;
1242                                                         continue;
1243                                                 }
1244                                                 if (!cl_decals.integer)
1245                                                 {
1246                                                         p->type = NULL;
1247                                                         continue;
1248                                                 }
1249                                                 // convert from a blood particle to a blood decal
1250                                                 VectorCopy(trace.plane.normal, p->vel);
1251                                                 VectorAdd(p->org, p->vel, p->org);
1252                                                 if (cl_stainmaps.integer)
1253                                                         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));
1254
1255                                                 p->type = particletype + pt_decal;
1256                                                 p->texnum = tex_blooddecal[rand()&7];
1257                                                 p->owner = hitent;
1258                                                 p->ownermodel = cl.entities[hitent].render.model;
1259                                                 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1260                                                 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1261                                                 p->time2 = cl.time;
1262                                                 p->alphafade = 0;
1263                                                 p->bounce = 0;
1264                                                 p->friction = 0;
1265                                                 p->gravity = 0;
1266                                                 p->size *= 2.0f;
1267                                         }
1268                                         else if (p->bounce < 0)
1269                                         {
1270                                                 // bounce -1 means remove on impact
1271                                                 p->type = NULL;
1272                                                 continue;
1273                                         }
1274                                         else
1275                                         {
1276                                                 // anything else - bounce off solid
1277                                                 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1278                                                 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1279                                                 if (DotProduct(p->vel, p->vel) < 0.03)
1280                                                         VectorClear(p->vel);
1281                                         }
1282                                 }
1283                         }
1284                         p->vel[2] -= p->gravity * gravity;
1285
1286                         if (p->friction)
1287                         {
1288                                 f = p->friction * frametime;
1289                                 if (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1290                                         f *= 4;
1291                                 f = 1.0f - f;
1292                                 VectorScale(p->vel, f, p->vel);
1293                         }
1294                 }
1295
1296                 if (p->type != particletype + pt_static)
1297                 {
1298                         switch (p->type - particletype)
1299                         {
1300                         case pt_entityparticle:
1301                                 // particle that removes itself after one rendered frame
1302                                 if (p->time2)
1303                                         p->type = NULL;
1304                                 else
1305                                         p->time2 = 1;
1306                                 break;
1307                         case pt_blood:
1308                                 a = CL_PointSuperContents(p->org);
1309                                 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1310                                 {
1311                                         p->size += frametime * 8;
1312                                         //p->alpha -= bloodwaterfade;
1313                                 }
1314                                 else
1315                                         p->vel[2] -= gravity;
1316                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1317                                         p->type = NULL;
1318                                 break;
1319                         case pt_bubble:
1320                                 a = CL_PointSuperContents(p->org);
1321                                 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1322                                 {
1323                                         p->type = NULL;
1324                                         break;
1325                                 }
1326                                 break;
1327                         case pt_rain:
1328                                 a = CL_PointSuperContents(p->org);
1329                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1330                                         p->type = NULL;
1331                                 break;
1332                         case pt_snow:
1333                                 if (cl.time > p->time2)
1334                                 {
1335                                         // snow flutter
1336                                         p->time2 = cl.time + (rand() & 3) * 0.1;
1337                                         p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1338                                         p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1339                                         //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1340                                 }
1341                                 a = CL_PointSuperContents(p->org);
1342                                 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1343                                         p->type = NULL;
1344                                 break;
1345                         case pt_smoke:
1346                                 //p->size += frametime * 15;
1347                                 break;
1348                         case pt_decal:
1349                                 // FIXME: this has fairly wacky handling of alpha
1350                                 p->alphafade = cl.time > (p->time2 + cl_decals_time.value) ? (255 / cl_decals_fadetime.value) : 0;
1351                                 if (cl.entities[p->owner].render.model == p->ownermodel)
1352                                 {
1353                                         Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1354                                         Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1355                                 }
1356                                 else
1357                                         p->type = NULL;
1358                                 break;
1359                         case pt_raindecal:
1360                                 a = max(0, (cl.time - p->time2) * 40);
1361                                 if (a < 16)
1362                                         p->texnum = tex_rainsplash[a];
1363                                 else
1364                                         p->type = NULL;
1365                                 break;
1366                         default:
1367                                 break;
1368                         }
1369                 }
1370         }
1371         cl.num_particles = maxparticle + 1;
1372         cl.free_particle = 0;
1373 }
1374
1375 #define MAX_PARTICLETEXTURES 64
1376 // particletexture_t is a rectangle in the particlefonttexture
1377 typedef struct particletexture_s
1378 {
1379         rtexture_t *texture;
1380         float s1, t1, s2, t2;
1381 }
1382 particletexture_t;
1383
1384 static rtexturepool_t *particletexturepool;
1385 static rtexture_t *particlefonttexture;
1386 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1387
1388 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1389
1390 #define PARTICLETEXTURESIZE 64
1391 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1392
1393 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1394 {
1395         float dz, f, dot;
1396         vec3_t normal;
1397         dz = 1 - (dx*dx+dy*dy);
1398         if (dz > 0) // it does hit the sphere
1399         {
1400                 f = 0;
1401                 // back side
1402                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1403                 VectorNormalize(normal);
1404                 dot = DotProduct(normal, light);
1405                 if (dot > 0.5) // interior reflection
1406                         f += ((dot *  2) - 1);
1407                 else if (dot < -0.5) // exterior reflection
1408                         f += ((dot * -2) - 1);
1409                 // front side
1410                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1411                 VectorNormalize(normal);
1412                 dot = DotProduct(normal, light);
1413                 if (dot > 0.5) // interior reflection
1414                         f += ((dot *  2) - 1);
1415                 else if (dot < -0.5) // exterior reflection
1416                         f += ((dot * -2) - 1);
1417                 f *= 128;
1418                 f += 16; // just to give it a haze so you can see the outline
1419                 f = bound(0, f, 255);
1420                 return (unsigned char) f;
1421         }
1422         else
1423                 return 0;
1424 }
1425
1426 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1427 {
1428         int basex, basey, y;
1429         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1430         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1431         particletexture[texnum].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1432         particletexture[texnum].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1433         particletexture[texnum].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1434         particletexture[texnum].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1435         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1436                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1437 }
1438
1439 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1440 {
1441         int x, y;
1442         float cx, cy, dx, dy, f, iradius;
1443         unsigned char *d;
1444         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1445         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1446         iradius = 1.0f / radius;
1447         alpha *= (1.0f / 255.0f);
1448         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1449         {
1450                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1451                 {
1452                         dx = (x - cx);
1453                         dy = (y - cy);
1454                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1455                         if (f > 0)
1456                         {
1457                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1458                                 d[0] += f * (red   - d[0]);
1459                                 d[1] += f * (green - d[1]);
1460                                 d[2] += f * (blue  - d[2]);
1461                         }
1462                 }
1463         }
1464 }
1465
1466 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1467 {
1468         int i;
1469         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1470         {
1471                 data[0] = bound(minr, data[0], maxr);
1472                 data[1] = bound(ming, data[1], maxg);
1473                 data[2] = bound(minb, data[2], maxb);
1474         }
1475 }
1476
1477 void particletextureinvert(unsigned char *data)
1478 {
1479         int i;
1480         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1481         {
1482                 data[0] = 255 - data[0];
1483                 data[1] = 255 - data[1];
1484                 data[2] = 255 - data[2];
1485         }
1486 }
1487
1488 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1489 static void R_InitBloodTextures (unsigned char *particletexturedata)
1490 {
1491         int i, j, k, m;
1492         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1493
1494         // blood particles
1495         for (i = 0;i < 8;i++)
1496         {
1497                 memset(&data[0][0][0], 255, sizeof(data));
1498                 for (k = 0;k < 24;k++)
1499                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1500                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1501                 particletextureinvert(&data[0][0][0]);
1502                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1503         }
1504
1505         // blood decals
1506         for (i = 0;i < 8;i++)
1507         {
1508                 memset(&data[0][0][0], 255, sizeof(data));
1509                 m = 8;
1510                 for (j = 1;j < 10;j++)
1511                         for (k = min(j, m - 1);k < m;k++)
1512                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1513                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1514                 particletextureinvert(&data[0][0][0]);
1515                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1516         }
1517
1518 }
1519
1520 static void R_InitParticleTexture (void)
1521 {
1522         int x, y, d, i, k, m;
1523         float dx, dy, radius, f, f2;
1524         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4], noise3[64][64], data2[64][16][4];
1525         vec3_t light;
1526         unsigned char *particletexturedata;
1527
1528         // a note: decals need to modulate (multiply) the background color to
1529         // properly darken it (stain), and they need to be able to alpha fade,
1530         // this is a very difficult challenge because it means fading to white
1531         // (no change to background) rather than black (darkening everything
1532         // behind the whole decal polygon), and to accomplish this the texture is
1533         // inverted (dark red blood on white background becomes brilliant cyan
1534         // and white on black background) so we can alpha fade it to black, then
1535         // we invert it again during the blendfunc to make it work...
1536
1537         particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1538         memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1539
1540         // smoke
1541         for (i = 0;i < 8;i++)
1542         {
1543                 memset(&data[0][0][0], 255, sizeof(data));
1544                 do
1545                 {
1546                         unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1547
1548                         fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1549                         fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1550                         m = 0;
1551                         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1552                         {
1553                                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1554                                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1555                                 {
1556                                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1557                                         d = (noise2[y][x] - 128) * 3 + 192;
1558                                         if (d > 0)
1559                                                 d = d * (1-(dx*dx+dy*dy));
1560                                         d = (d * noise1[y][x]) >> 7;
1561                                         d = bound(0, d, 255);
1562                                         data[y][x][3] = (unsigned char) d;
1563                                         if (m < d)
1564                                                 m = d;
1565                                 }
1566                         }
1567                 }
1568                 while (m < 224);
1569                 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1570         }
1571
1572         // rain splash
1573         for (i = 0;i < 16;i++)
1574         {
1575                 memset(&data[0][0][0], 255, sizeof(data));
1576                 radius = i * 3.0f / 4.0f / 16.0f;
1577                 f2 = 255.0f * ((15.0f - i) / 15.0f);
1578                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1579                 {
1580                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1581                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1582                         {
1583                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1584                                 f = f2 * (1.0 - 4.0f * fabs(radius - sqrt(dx*dx+dy*dy)));
1585                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1586                         }
1587                 }
1588                 setuptex(tex_rainsplash[i], &data[0][0][0], particletexturedata);
1589         }
1590
1591         // normal particle
1592         memset(&data[0][0][0], 255, sizeof(data));
1593         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1594         {
1595                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1596                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1597                 {
1598                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1599                         d = 256 * (1 - (dx*dx+dy*dy));
1600                         d = bound(0, d, 255);
1601                         data[y][x][3] = (unsigned char) d;
1602                 }
1603         }
1604         setuptex(tex_particle, &data[0][0][0], particletexturedata);
1605
1606         // rain
1607         memset(&data[0][0][0], 255, sizeof(data));
1608         light[0] = 1;light[1] = 1;light[2] = 1;
1609         VectorNormalize(light);
1610         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1611         {
1612                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1613                 // stretch upper half of bubble by +50% and shrink lower half by -50%
1614                 // (this gives an elongated teardrop shape)
1615                 if (dy > 0.5f)
1616                         dy = (dy - 0.5f) * 2.0f;
1617                 else
1618                         dy = (dy - 0.5f) / 1.5f;
1619                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1620                 {
1621                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1622                         // shrink bubble width to half
1623                         dx *= 2.0f;
1624                         data[y][x][3] = shadebubble(dx, dy, light);
1625                 }
1626         }
1627         setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1628
1629         // bubble
1630         memset(&data[0][0][0], 255, sizeof(data));
1631         light[0] = 1;light[1] = 1;light[2] = 1;
1632         VectorNormalize(light);
1633         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1634         {
1635                 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1636                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1637                 {
1638                         dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1639                         data[y][x][3] = shadebubble(dx, dy, light);
1640                 }
1641         }
1642         setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1643
1644         // Blood particles and blood decals
1645         R_InitBloodTextures (particletexturedata);
1646
1647         // bullet decals
1648         for (i = 0;i < 8;i++)
1649         {
1650                 memset(&data[0][0][0], 255, sizeof(data));
1651                 for (k = 0;k < 12;k++)
1652                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1653                 for (k = 0;k < 3;k++)
1654                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1655                 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1656                 particletextureinvert(&data[0][0][0]);
1657                 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1658         }
1659
1660 #if 0
1661         Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1662 #endif
1663
1664         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1665         if (!particlefonttexture)
1666                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1667         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1668                 particletexture[i].texture = particlefonttexture;
1669
1670         // nexbeam
1671         fractalnoise(&noise3[0][0], 64, 4);
1672         m = 0;
1673         for (y = 0;y < 64;y++)
1674         {
1675                 dy = (y - 0.5f*64) / (64*0.5f-1);
1676                 for (x = 0;x < 16;x++)
1677                 {
1678                         dx = (x - 0.5f*16) / (16*0.5f-2);
1679                         d = (1 - sqrt(fabs(dx))) * noise3[y][x];
1680                         data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1681                         data2[y][x][3] = 255;
1682                 }
1683         }
1684
1685 #if 0
1686         Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1687 #endif
1688
1689         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1690         if (!particletexture[tex_beam].texture)
1691                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
1692         particletexture[tex_beam].s1 = 0;
1693         particletexture[tex_beam].t1 = 0;
1694         particletexture[tex_beam].s2 = 1;
1695         particletexture[tex_beam].t2 = 1;
1696         Mem_Free(particletexturedata);
1697 }
1698
1699 static void r_part_start(void)
1700 {
1701         particletexturepool = R_AllocTexturePool();
1702         R_InitParticleTexture ();
1703 }
1704
1705 static void r_part_shutdown(void)
1706 {
1707         R_FreeTexturePool(&particletexturepool);
1708 }
1709
1710 static void r_part_newmap(void)
1711 {
1712 }
1713
1714 void R_Particles_Init (void)
1715 {
1716         Cvar_RegisterVariable(&r_drawparticles);
1717         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1718 }
1719
1720 float particle_vertex3f[12], particle_texcoord2f[8];
1721
1722 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight)
1723 {
1724         const particle_t *p = cl.particles + surfacenumber;
1725         rmeshstate_t m;
1726         pblend_t blendmode;
1727         float org[3], up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
1728         particletexture_t *tex;
1729
1730         VectorCopy(p->org, org);
1731
1732         blendmode = p->type->blendmode;
1733         tex = &particletexture[p->texnum];
1734         cr = p->color[0] * (1.0f / 255.0f);
1735         cg = p->color[1] * (1.0f / 255.0f);
1736         cb = p->color[2] * (1.0f / 255.0f);
1737         ca = p->alpha * (1.0f / 255.0f);
1738         if (blendmode == PBLEND_MOD)
1739         {
1740                 cr *= ca;
1741                 cg *= ca;
1742                 cb *= ca;
1743                 cr = min(cr, 1);
1744                 cg = min(cg, 1);
1745                 cb = min(cb, 1);
1746                 ca = 1;
1747         }
1748         ca /= cl_particles_quality.value;
1749         if (p->type->lighting)
1750         {
1751                 float ambient[3], diffuse[3], diffusenormal[3];
1752                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, org, true);
1753                 cr *= (ambient[0] + 0.5 * diffuse[0]);
1754                 cg *= (ambient[1] + 0.5 * diffuse[1]);
1755                 cb *= (ambient[2] + 0.5 * diffuse[2]);
1756         }
1757         if (fogenabled)
1758         {
1759                 fog = VERTEXFOGTABLE(VectorDistance(org, r_vieworigin));
1760                 ifog = 1 - fog;
1761                 cr = cr * ifog;
1762                 cg = cg * ifog;
1763                 cb = cb * ifog;
1764                 if (blendmode == PBLEND_ALPHA)
1765                 {
1766                         cr += fogcolor[0] * fog;
1767                         cg += fogcolor[1] * fog;
1768                         cb += fogcolor[2] * fog;
1769                 }
1770         }
1771
1772         R_Mesh_Matrix(&identitymatrix);
1773
1774         memset(&m, 0, sizeof(m));
1775         m.tex[0] = R_GetTexture(tex->texture);
1776         m.pointer_texcoord[0] = particle_texcoord2f;
1777         m.pointer_vertex = particle_vertex3f;
1778         R_Mesh_State(&m);
1779
1780         GL_Color(cr, cg, cb, ca);
1781
1782         if (blendmode == PBLEND_ALPHA)
1783                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1784         else if (blendmode == PBLEND_ADD)
1785                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
1786         else //if (blendmode == PBLEND_MOD)
1787                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1788         GL_DepthMask(false);
1789         GL_DepthTest(true);
1790         size = p->size * cl_particles_size.value;
1791         if (p->type->orientation == PARTICLE_BILLBOARD || p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1792         {
1793                 if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
1794                 {
1795                         // double-sided
1796                         if (DotProduct(p->vel, r_vieworigin) > DotProduct(p->vel, org))
1797                         {
1798                                 VectorNegate(p->vel, v);
1799                                 VectorVectors(v, right, up);
1800                         }
1801                         else
1802                                 VectorVectors(p->vel, right, up);
1803                         VectorScale(right, size, right);
1804                         VectorScale(up, size, up);
1805                 }
1806                 else
1807                 {
1808                         VectorScale(r_viewleft, -size, right);
1809                         VectorScale(r_viewup, size, up);
1810                 }
1811                 particle_vertex3f[ 0] = org[0] - right[0] - up[0];
1812                 particle_vertex3f[ 1] = org[1] - right[1] - up[1];
1813                 particle_vertex3f[ 2] = org[2] - right[2] - up[2];
1814                 particle_vertex3f[ 3] = org[0] - right[0] + up[0];
1815                 particle_vertex3f[ 4] = org[1] - right[1] + up[1];
1816                 particle_vertex3f[ 5] = org[2] - right[2] + up[2];
1817                 particle_vertex3f[ 6] = org[0] + right[0] + up[0];
1818                 particle_vertex3f[ 7] = org[1] + right[1] + up[1];
1819                 particle_vertex3f[ 8] = org[2] + right[2] + up[2];
1820                 particle_vertex3f[ 9] = org[0] + right[0] - up[0];
1821                 particle_vertex3f[10] = org[1] + right[1] - up[1];
1822                 particle_vertex3f[11] = org[2] + right[2] - up[2];
1823                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
1824                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
1825                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
1826                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
1827         }
1828         else if (p->type->orientation == PARTICLE_SPARK)
1829         {
1830                 VectorMA(p->org, -0.02, p->vel, v);
1831                 VectorMA(p->org, 0.02, p->vel, up2);
1832                 R_CalcBeam_Vertex3f(particle_vertex3f, v, up2, size);
1833                 particle_texcoord2f[0] = tex->s1;particle_texcoord2f[1] = tex->t2;
1834                 particle_texcoord2f[2] = tex->s1;particle_texcoord2f[3] = tex->t1;
1835                 particle_texcoord2f[4] = tex->s2;particle_texcoord2f[5] = tex->t1;
1836                 particle_texcoord2f[6] = tex->s2;particle_texcoord2f[7] = tex->t2;
1837         }
1838         else if (p->type->orientation == PARTICLE_BEAM)
1839         {
1840                 R_CalcBeam_Vertex3f(particle_vertex3f, p->org, p->vel, size);
1841                 VectorSubtract(p->vel, p->org, up);
1842                 VectorNormalize(up);
1843                 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
1844                 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
1845                 particle_texcoord2f[0] = 1;particle_texcoord2f[1] = v[0];
1846                 particle_texcoord2f[2] = 0;particle_texcoord2f[3] = v[0];
1847                 particle_texcoord2f[4] = 0;particle_texcoord2f[5] = v[1];
1848                 particle_texcoord2f[6] = 1;particle_texcoord2f[7] = v[1];
1849         }
1850         else
1851         {
1852                 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
1853                 return;
1854         }
1855
1856         R_Mesh_Draw(0, 4, 2, polygonelements);
1857 }
1858
1859 void R_DrawParticles (void)
1860 {
1861         int i;
1862         float minparticledist;
1863         particle_t *p;
1864
1865         // LordHavoc: early out conditions
1866         if ((!cl.num_particles) || (!r_drawparticles.integer))
1867                 return;
1868
1869         minparticledist = DotProduct(r_vieworigin, r_viewforward) + 4.0f;
1870
1871         // LordHavoc: only render if not too close
1872         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1873         {
1874                 if (p->type)
1875                 {
1876                         renderstats.particles++;
1877                         if (DotProduct(p->org, r_viewforward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
1878                         {
1879                                 if (p->type == particletype + pt_decal)
1880                                         R_DrawParticle_TransparentCallback(0, i, 0);
1881                                 else
1882                                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
1883                         }
1884                 }
1885         }
1886 }
1887