]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
merged CL_MoveParticles, CL_MoveDecals, and R_MoveExplosions into their
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #include "cl_collision.h"
24 #include "image.h"
25 #include "r_shadow.h"
26
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
29 {
30         {0, 0, false}, // pt_dead
31         {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33         {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34         {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
35         {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36         {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39         {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
40         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41         {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42         {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
43 };
44
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
47
48 typedef struct particleeffectinfo_s
49 {
50         int effectnameindex; // which effect this belongs to
51         // PARTICLEEFFECT_* bits
52         int flags;
53         // blood effects may spawn very few particles, so proper fraction-overflow
54         // handling is very important, this variable keeps track of the fraction
55         double particleaccumulator;
56         // the math is: countabsolute + requestedcount * countmultiplier * quality
57         // absolute number of particles to spawn, often used for decals
58         // (unaffected by quality and requestedcount)
59         float countabsolute;
60         // multiplier for the number of particles CL_ParticleEffect was told to
61         // spawn, most effects do not really have a count and hence use 1, so
62         // this is often the actual count to spawn, not merely a multiplier
63         float countmultiplier;
64         // if > 0 this causes the particle to spawn in an evenly spaced line from
65         // originmins to originmaxs (causing them to describe a trail, not a box)
66         float trailspacing;
67         // type of particle to spawn (defines some aspects of behavior)
68         ptype_t particletype;
69         // range of colors to choose from in hex RRGGBB (like HTML color tags),
70         // randomly interpolated at spawn
71         unsigned int color[2];
72         // a random texture is chosen in this range (note the second value is one
73         // past the last choosable, so for example 8,16 chooses any from 8 up and
74         // including 15)
75         // if start and end of the range are the same, no randomization is done
76         int tex[2];
77         // range of size values randomly chosen when spawning, plus size increase over time
78         float size[3];
79         // range of alpha values randomly chosen when spawning, plus alpha fade
80         float alpha[3];
81         // how long the particle should live (note it is also removed if alpha drops to 0)
82         float time[2];
83         // how much gravity affects this particle (negative makes it fly up!)
84         float gravity;
85         // how much bounce the particle has when it hits a surface
86         // if negative the particle is removed on impact
87         float bounce;
88         // if in air this friction is applied
89         // if negative the particle accelerates
90         float airfriction;
91         // if in liquid (water/slime/lava) this friction is applied
92         // if negative the particle accelerates
93         float liquidfriction;
94         // these offsets are added to the values given to particleeffect(), and
95         // then an ellipsoid-shaped jitter is added as defined by these
96         // (they are the 3 radii)
97         float originoffset[3];
98         float velocityoffset[3];
99         float originjitter[3];
100         float velocityjitter[3];
101         float velocitymultiplier;
102         // an effect can also spawn a dlight
103         float lightradiusstart;
104         float lightradiusfade;
105         float lighttime;
106         float lightcolor[3];
107         qboolean lightshadow;
108         int lightcubemapnum;
109 }
110 particleeffectinfo_t;
111
112 #define MAX_PARTICLEEFFECTNAME 256
113 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
114
115 #define MAX_PARTICLEEFFECTINFO 4096
116
117 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
118
119 static int particlepalette[256] =
120 {
121         0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
122         0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
123         0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
124         0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
125         0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
126         0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
127         0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
128         0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
129         0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
130         0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
131         0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
132         0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
133         0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
134         0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
135         0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
136         0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
137         0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
138         0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
139         0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
140         0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
141         0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
142         0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
143         0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
144         0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
145         0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
146         0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
147         0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
148         0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
149         0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
150         0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
151         0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
152         0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53  // 248-255
153 };
154
155 int             ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
156 int             ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
157 int             ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
158
159 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
160
161 // texture numbers in particle font
162 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
163 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
164 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
165 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
166 static const int tex_rainsplash = 32;
167 static const int tex_particle = 63;
168 static const int tex_bubble = 62;
169 static const int tex_raindrop = 61;
170 static const int tex_beam = 60;
171
172 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
173 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
174 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
175 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
176 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
177 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
178 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
179 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
180 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
181 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
182 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
183 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
184 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
185 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
186 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
187 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
188 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
189 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
190 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
191 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
192 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
193
194
195 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
196 {
197         int arrayindex;
198         int argc;
199         int effectinfoindex;
200         int linenumber;
201         particleeffectinfo_t *info = NULL;
202         const char *text = textstart;
203         char argv[16][1024];
204         effectinfoindex = -1;
205         for (linenumber = 1;;linenumber++)
206         {
207                 argc = 0;
208                 for (arrayindex = 0;arrayindex < 16;arrayindex++)
209                         argv[arrayindex][0] = 0;
210                 for (;;)
211                 {
212                         if (!COM_ParseToken_Simple(&text, true, false))
213                                 return;
214                         if (!strcmp(com_token, "\n"))
215                                 break;
216                         if (argc < 16)
217                         {
218                                 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
219                                 argc++;
220                         }
221                 }
222                 if (argc < 1)
223                         continue;
224 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
225 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
226 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
227 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
228 #define readfloat(var) checkparms(2);var = atof(argv[1])
229                 if (!strcmp(argv[0], "effect"))
230                 {
231                         int effectnameindex;
232                         checkparms(2);
233                         effectinfoindex++;
234                         if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
235                         {
236                                 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
237                                 break;
238                         }
239                         for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
240                         {
241                                 if (particleeffectname[effectnameindex][0])
242                                 {
243                                         if (!strcmp(particleeffectname[effectnameindex], argv[1]))
244                                                 break;
245                                 }
246                                 else
247                                 {
248                                         strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
249                                         break;
250                                 }
251                         }
252                         // if we run out of names, abort
253                         if (effectnameindex == MAX_PARTICLEEFFECTNAME)
254                         {
255                                 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
256                                 break;
257                         }
258                         info = particleeffectinfo + effectinfoindex;
259                         info->effectnameindex = effectnameindex;
260                         info->particletype = pt_alphastatic;
261                         info->tex[0] = tex_particle;
262                         info->tex[1] = tex_particle;
263                         info->color[0] = 0xFFFFFF;
264                         info->color[1] = 0xFFFFFF;
265                         info->size[0] = 1;
266                         info->size[1] = 1;
267                         info->alpha[0] = 0;
268                         info->alpha[1] = 256;
269                         info->alpha[2] = 256;
270                         info->time[0] = 9999;
271                         info->time[1] = 9999;
272                         VectorSet(info->lightcolor, 1, 1, 1);
273                         info->lightshadow = true;
274                         info->lighttime = 9999;
275                 }
276                 else if (info == NULL)
277                 {
278                         Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
279                         break;
280                 }
281                 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
282                 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
283                 else if (!strcmp(argv[0], "type"))
284                 {
285                         checkparms(2);
286                         if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
287                         else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
288                         else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
289                         else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
290                         else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
291                         else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
292                         else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
293                         else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
294                         else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
295                         else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
296                         else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
297                         else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
298                         else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
299                 }
300                 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
301                 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
302                 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
303                 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
304                 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
305                 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
306                 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
307                 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
308                 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
309                 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
310                 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
311                 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
312                 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
313                 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
314                 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
315                 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
316                 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
317                 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
318                 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
319                 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
320                 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
321                 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
322                 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
323                 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
324                 else
325                         Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
326 #undef checkparms
327 #undef readints
328 #undef readfloats
329 #undef readint
330 #undef readfloat
331         }
332 }
333
334 int CL_ParticleEffectIndexForName(const char *name)
335 {
336         int i;
337         for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
338                 if (!strcmp(particleeffectname[i], name))
339                         return i;
340         return 0;
341 }
342
343 const char *CL_ParticleEffectNameForIndex(int i)
344 {
345         if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
346                 return NULL;
347         return particleeffectname[i];
348 }
349
350 // MUST match effectnameindex_t in client.h
351 static const char *standardeffectnames[EFFECT_TOTAL] =
352 {
353         "",
354         "TE_GUNSHOT",
355         "TE_GUNSHOTQUAD",
356         "TE_SPIKE",
357         "TE_SPIKEQUAD",
358         "TE_SUPERSPIKE",
359         "TE_SUPERSPIKEQUAD",
360         "TE_WIZSPIKE",
361         "TE_KNIGHTSPIKE",
362         "TE_EXPLOSION",
363         "TE_EXPLOSIONQUAD",
364         "TE_TAREXPLOSION",
365         "TE_TELEPORT",
366         "TE_LAVASPLASH",
367         "TE_SMALLFLASH",
368         "TE_FLAMEJET",
369         "EF_FLAME",
370         "TE_BLOOD",
371         "TE_SPARK",
372         "TE_PLASMABURN",
373         "TE_TEI_G3",
374         "TE_TEI_SMOKE",
375         "TE_TEI_BIGEXPLOSION",
376         "TE_TEI_PLASMAHIT",
377         "EF_STARDUST",
378         "TR_ROCKET",
379         "TR_GRENADE",
380         "TR_BLOOD",
381         "TR_WIZSPIKE",
382         "TR_SLIGHTBLOOD",
383         "TR_KNIGHTSPIKE",
384         "TR_VORESPIKE",
385         "TR_NEHAHRASMOKE",
386         "TR_NEXUIZPLASMA",
387         "TR_GLOWTRAIL",
388         "SVC_PARTICLE"
389 };
390
391 void CL_Particles_LoadEffectInfo(void)
392 {
393         int i;
394         unsigned char *filedata;
395         fs_offset_t filesize;
396         memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
397         memset(particleeffectname, 0, sizeof(particleeffectname));
398         for (i = 0;i < EFFECT_TOTAL;i++)
399                 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
400         filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
401         if (filedata)
402         {
403                 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
404                 Mem_Free(filedata);
405         }
406 };
407
408 /*
409 ===============
410 CL_InitParticles
411 ===============
412 */
413 void CL_ReadPointFile_f (void);
414 void CL_Particles_Init (void)
415 {
416         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)");
417         Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
418
419         Cvar_RegisterVariable (&cl_particles);
420         Cvar_RegisterVariable (&cl_particles_quality);
421         Cvar_RegisterVariable (&cl_particles_alpha);
422         Cvar_RegisterVariable (&cl_particles_size);
423         Cvar_RegisterVariable (&cl_particles_quake);
424         Cvar_RegisterVariable (&cl_particles_blood);
425         Cvar_RegisterVariable (&cl_particles_blood_alpha);
426         Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
427         Cvar_RegisterVariable (&cl_particles_explosions_sparks);
428         Cvar_RegisterVariable (&cl_particles_explosions_shell);
429         Cvar_RegisterVariable (&cl_particles_bulletimpacts);
430         Cvar_RegisterVariable (&cl_particles_rain);
431         Cvar_RegisterVariable (&cl_particles_snow);
432         Cvar_RegisterVariable (&cl_particles_smoke);
433         Cvar_RegisterVariable (&cl_particles_smoke_alpha);
434         Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
435         Cvar_RegisterVariable (&cl_particles_sparks);
436         Cvar_RegisterVariable (&cl_particles_bubbles);
437         Cvar_RegisterVariable (&cl_decals);
438         Cvar_RegisterVariable (&cl_decals_time);
439         Cvar_RegisterVariable (&cl_decals_fadetime);
440 }
441
442 void CL_Particles_Shutdown (void)
443 {
444 }
445
446 // list of all 26 parameters:
447 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
448 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
449 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
450 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
451 // palpha - opacity of particle as 0-255 (can be more than 255)
452 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
453 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
454 // pgravity - how much effect gravity has on the particle (0-1)
455 // 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
456 // px,py,pz - starting origin of particle
457 // pvx,pvy,pvz - starting velocity of particle
458 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
459 static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
460 {
461         int l1, l2;
462         particle_t *part;
463         vec3_t v;
464         if (!cl_particles.integer)
465                 return NULL;
466         for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
467         if (cl.free_particle >= cl.max_particles)
468                 return NULL;
469         part = &cl.particles[cl.free_particle++];
470         if (cl.num_particles < cl.free_particle)
471                 cl.num_particles = cl.free_particle;
472         memset(part, 0, sizeof(*part));
473         part->typeindex = ptypeindex;
474         l2 = (int)lhrandom(0.5, 256.5);
475         l1 = 256 - l2;
476         part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
477         part->color[1] = ((((pcolor1 >>  8) & 0xFF) * l1 + ((pcolor2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
478         part->color[2] = ((((pcolor1 >>  0) & 0xFF) * l1 + ((pcolor2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
479         part->color[3] = 0xFF;
480         part->texnum = ptex;
481         part->size = psize;
482         part->sizeincrease = psizeincrease;
483         part->alpha = palpha;
484         part->alphafade = palphafade;
485         part->gravity = pgravity;
486         part->bounce = pbounce;
487         VectorRandom(v);
488         part->org[0] = px + originjitter * v[0];
489         part->org[1] = py + originjitter * v[1];
490         part->org[2] = pz + originjitter * v[2];
491         part->vel[0] = pvx + velocityjitter * v[0];
492         part->vel[1] = pvy + velocityjitter * v[1];
493         part->vel[2] = pvz + velocityjitter * v[2];
494         part->time2 = 0;
495         part->airfriction = pairfriction;
496         part->liquidfriction = pliquidfriction;
497         part->die = cl.time + part->alpha / (part->alphafade ? part->alphafade : 1);
498         part->delayedcollisions = 0;
499         if (part->typeindex == pt_blood)
500                 part->gravity += 1; // FIXME: this is a legacy hack, effectinfo.txt doesn't have gravity on blood (nor do the particle calls in the engine)
501         // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
502         if (part->typeindex == pt_rain)
503         {
504                 int i;
505                 particle_t *part2;
506                 float lifetime = part->die - cl.time;
507                 vec3_t endvec;
508                 trace_t trace;
509                 // turn raindrop into simple spark and create delayedspawn splash effect
510                 part->typeindex = pt_spark;
511                 part->bounce = 0;
512                 VectorMA(part->org, lifetime, part->vel, endvec);
513                 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
514                 part->die = cl.time + lifetime * trace.fraction;
515                 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0);
516                 if (part2)
517                 {
518                         part2->delayedspawn = part->die;
519                         part2->die += part->die - cl.time;
520                         for (i = rand() & 7;i < 10;i++)
521                         {
522                                 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32);
523                                 if (part2)
524                                 {
525                                         part2->delayedspawn = part->die;
526                                         part2->die += part->die - cl.time;
527                                 }
528                         }
529                 }
530         }
531         else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
532         {
533                 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
534                 vec3_t endvec;
535                 trace_t trace;
536                 VectorMA(part->org, lifetime, part->vel, endvec);
537                 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
538                 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
539         }
540         return part;
541 }
542
543 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
544 {
545         int l1, l2;
546         decal_t *decal;
547         if (!cl_decals.integer)
548                 return;
549         for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
550         if (cl.free_decal >= cl.max_decals)
551                 return;
552         decal = &cl.decals[cl.free_decal++];
553         if (cl.num_decals < cl.free_decal)
554                 cl.num_decals = cl.free_decal;
555         memset(decal, 0, sizeof(*decal));
556         decal->typeindex = pt_decal;
557         decal->texnum = texnum;
558         VectorAdd(org, normal, decal->org);
559         VectorCopy(normal, decal->normal);
560         decal->size = size;
561         decal->alpha = alpha;
562         decal->time2 = cl.time;
563         l2 = (int)lhrandom(0.5, 256.5);
564         l1 = 256 - l2;
565         decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
566         decal->color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
567         decal->color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
568         decal->color[3] = 0xFF;
569         decal->owner = hitent;
570         if (hitent)
571         {
572                 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
573                 decal->ownermodel = cl.entities[decal->owner].render.model;
574                 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
575                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
576         }
577 }
578
579 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
580 {
581         int i;
582         float bestfrac, bestorg[3], bestnormal[3];
583         float org2[3];
584         int besthitent = 0, hitent;
585         trace_t trace;
586         bestfrac = 10;
587         for (i = 0;i < 32;i++)
588         {
589                 VectorRandom(org2);
590                 VectorMA(org, maxdist, org2, org2);
591                 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
592                 // take the closest trace result that doesn't end up hitting a NOMARKS
593                 // surface (sky for example)
594                 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
595                 {
596                         bestfrac = trace.fraction;
597                         besthitent = hitent;
598                         VectorCopy(trace.endpos, bestorg);
599                         VectorCopy(trace.plane.normal, bestnormal);
600                 }
601         }
602         if (bestfrac < 1)
603                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
604 }
605
606 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
607 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
608 void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
609 {
610         vec3_t center;
611         matrix4x4_t tempmatrix;
612         VectorLerp(originmins, 0.5, originmaxs, center);
613         Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
614         if (effectnameindex == EFFECT_SVC_PARTICLE)
615         {
616                 if (cl_particles.integer)
617                 {
618                         // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
619                         if (count == 1024)
620                                 CL_ParticleExplosion(center);
621                         else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
622                                 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
623                         else
624                         {
625                                 count *= cl_particles_quality.value;
626                                 for (;count > 0;count--)
627                                 {
628                                         int k = particlepalette[palettecolor + (rand()&7)];
629                                         if (cl_particles_quake.integer)
630                                                 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
631                                         else if (gamemode == GAME_GOODVSBAD2)
632                                                 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
633                                         else
634                                                 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
635                                 }
636                         }
637                 }
638         }
639         else if (effectnameindex == EFFECT_TE_WIZSPIKE)
640                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
641         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
642                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
643         else if (effectnameindex == EFFECT_TE_SPIKE)
644         {
645                 if (cl_particles_bulletimpacts.integer)
646                 {
647                         if (cl_particles_quake.integer)
648                         {
649                                 if (cl_particles_smoke.integer)
650                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
651                         }
652                         else
653                         {
654                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
655                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
656                         }
657                 }
658                 // bullet hole
659                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
660                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
661         }
662         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
663         {
664                 if (cl_particles_bulletimpacts.integer)
665                 {
666                         if (cl_particles_quake.integer)
667                         {
668                                 if (cl_particles_smoke.integer)
669                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
670                         }
671                         else
672                         {
673                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
674                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
675                         }
676                 }
677                 // bullet hole
678                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
679                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
680                 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
681         }
682         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
683         {
684                 if (cl_particles_bulletimpacts.integer)
685                 {
686                         if (cl_particles_quake.integer)
687                         {
688                                 if (cl_particles_smoke.integer)
689                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
690                         }
691                         else
692                         {
693                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
694                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
695                         }
696                 }
697                 // bullet hole
698                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
699                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
700         }
701         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
702         {
703                 if (cl_particles_bulletimpacts.integer)
704                 {
705                         if (cl_particles_quake.integer)
706                         {
707                                 if (cl_particles_smoke.integer)
708                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
709                         }
710                         else
711                         {
712                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
713                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
714                         }
715                 }
716                 // bullet hole
717                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
718                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
719                 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
720         }
721         else if (effectnameindex == EFFECT_TE_BLOOD)
722         {
723                 if (!cl_particles_blood.integer)
724                         return;
725                 if (cl_particles_quake.integer)
726                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
727                 else
728                 {
729                         static double bloodaccumulator = 0;
730                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
731                         for (;bloodaccumulator > 0;bloodaccumulator--)
732                                 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
733                 }
734         }
735         else if (effectnameindex == EFFECT_TE_SPARK)
736                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
737         else if (effectnameindex == EFFECT_TE_PLASMABURN)
738         {
739                 // plasma scorch mark
740                 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
741                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
742                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
743         }
744         else if (effectnameindex == EFFECT_TE_GUNSHOT)
745         {
746                 if (cl_particles_bulletimpacts.integer)
747                 {
748                         if (cl_particles_quake.integer)
749                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
750                         else
751                         {
752                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
753                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
754                         }
755                 }
756                 // bullet hole
757                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
758                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
759         }
760         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
761         {
762                 if (cl_particles_bulletimpacts.integer)
763                 {
764                         if (cl_particles_quake.integer)
765                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
766                         else
767                         {
768                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
769                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
770                         }
771                 }
772                 // bullet hole
773                 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
774                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
775                 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
776         }
777         else if (effectnameindex == EFFECT_TE_EXPLOSION)
778         {
779                 CL_ParticleExplosion(center);
780                 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
781         }
782         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
783         {
784                 CL_ParticleExplosion(center);
785                 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
786         }
787         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
788         {
789                 if (cl_particles_quake.integer)
790                 {
791                         int i;
792                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
793                         {
794                                 if (i & 1)
795                                         CL_NewParticle(pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
796                                 else
797                                         CL_NewParticle(pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
798                         }
799                 }
800                 else
801                         CL_ParticleExplosion(center);
802                 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
803         }
804         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
805                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
806         else if (effectnameindex == EFFECT_TE_FLAMEJET)
807         {
808                 count *= cl_particles_quality.value;
809                 while (count-- > 0)
810                         CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
811         }
812         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
813         {
814                 float i, j, inc, vel;
815                 vec3_t dir, org;
816
817                 inc = 8 / cl_particles_quality.value;
818                 for (i = -128;i < 128;i += inc)
819                 {
820                         for (j = -128;j < 128;j += inc)
821                         {
822                                 dir[0] = j + lhrandom(0, inc);
823                                 dir[1] = i + lhrandom(0, inc);
824                                 dir[2] = 256;
825                                 org[0] = center[0] + dir[0];
826                                 org[1] = center[1] + dir[1];
827                                 org[2] = center[2] + lhrandom(0, 64);
828                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
829                                 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 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, 0);
830                         }
831                 }
832         }
833         else if (effectnameindex == EFFECT_TE_TELEPORT)
834         {
835                 float i, j, k, inc, vel;
836                 vec3_t dir;
837
838                 inc = 8 / cl_particles_quality.value;
839                 for (i = -16;i < 16;i += inc)
840                 {
841                         for (j = -16;j < 16;j += inc)
842                         {
843                                 for (k = -24;k < 32;k += inc)
844                                 {
845                                         VectorSet(dir, i*8, j*8, k*8);
846                                         VectorNormalize(dir);
847                                         vel = lhrandom(50, 113);
848                                         CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
849                                 }
850                         }
851                 }
852                 CL_NewParticle(pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
853                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
854         }
855         else if (effectnameindex == EFFECT_TE_TEI_G3)
856                 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
857         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
858         {
859                 if (cl_particles_smoke.integer)
860                 {
861                         count *= 0.25f * cl_particles_quality.value;
862                         while (count-- > 0)
863                                 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
864                 }
865         }
866         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
867         {
868                 CL_ParticleExplosion(center);
869                 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
870         }
871         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
872         {
873                 float f;
874                 if (cl_stainmaps.integer)
875                         R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
876                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
877                 if (cl_particles_smoke.integer)
878                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
879                                 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
880                 if (cl_particles_sparks.integer)
881                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
882                                 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
883                 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
884         }
885         else if (effectnameindex == EFFECT_EF_FLAME)
886         {
887                 count *= 300 * cl_particles_quality.value;
888                 while (count-- > 0)
889                         CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
890                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
891         }
892         else if (effectnameindex == EFFECT_EF_STARDUST)
893         {
894                 count *= 200 * cl_particles_quality.value;
895                 while (count-- > 0)
896                         CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
897                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
898         }
899         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
900         {
901                 vec3_t dir, pos;
902                 float len, dec, qd;
903                 int smoke, blood, bubbles, r, color;
904
905                 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
906                 {
907                         vec4_t light;
908                         Vector4Set(light, 0, 0, 0, 0);
909
910                         if (effectnameindex == EFFECT_TR_ROCKET)
911                                 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
912                         else if (effectnameindex == EFFECT_TR_VORESPIKE)
913                         {
914                                 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
915                                         Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
916                                 else
917                                         Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
918                         }
919                         else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
920                                 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
921
922                         if (light[3])
923                         {
924                                 matrix4x4_t tempmatrix;
925                                 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
926                                 R_RTLight_Update(&r_refdef.scene.lights[r_refdef.scene.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
927                         }
928                 }
929
930                 if (!spawnparticles)
931                         return;
932
933                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
934                         return;
935
936                 VectorSubtract(originmaxs, originmins, dir);
937                 len = VectorNormalizeLength(dir);
938                 if (ent)
939                 {
940                         dec = -ent->persistent.trail_time;
941                         ent->persistent.trail_time += len;
942                         if (ent->persistent.trail_time < 0.01f)
943                                 return;
944
945                         // if we skip out, leave it reset
946                         ent->persistent.trail_time = 0.0f;
947                 }
948                 else
949                         dec = 0;
950
951                 // advance into this frame to reach the first puff location
952                 VectorMA(originmins, dec, dir, pos);
953                 len -= dec;
954
955                 smoke = cl_particles.integer && cl_particles_smoke.integer;
956                 blood = cl_particles.integer && cl_particles_blood.integer;
957                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
958                 qd = 1.0f / cl_particles_quality.value;
959
960                 while (len >= 0)
961                 {
962                         dec = 3;
963                         if (blood)
964                         {
965                                 if (effectnameindex == EFFECT_TR_BLOOD)
966                                 {
967                                         if (cl_particles_quake.integer)
968                                         {
969                                                 color = particlepalette[67 + (rand()&3)];
970                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
971                                         }
972                                         else
973                                         {
974                                                 dec = 16;
975                                                 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
976                                         }
977                                 }
978                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
979                                 {
980                                         if (cl_particles_quake.integer)
981                                         {
982                                                 dec = 6;
983                                                 color = particlepalette[67 + (rand()&3)];
984                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
985                                         }
986                                         else
987                                         {
988                                                 dec = 32;
989                                                 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
990                                         }
991                                 }
992                         }
993                         if (smoke)
994                         {
995                                 if (effectnameindex == EFFECT_TR_ROCKET)
996                                 {
997                                         if (cl_particles_quake.integer)
998                                         {
999                                                 r = rand()&3;
1000                                                 color = particlepalette[ramp3[r]];
1001                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
1002                                         }
1003                                         else
1004                                         {
1005                                                 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, 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, 0);
1006                                                 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, 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, 0, 20);
1007                                         }
1008                                 }
1009                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1010                                 {
1011                                         if (cl_particles_quake.integer)
1012                                         {
1013                                                 r = 2 + (rand()%5);
1014                                                 color = particlepalette[ramp3[r]];
1015                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
1016                                         }
1017                                         else
1018                                         {
1019                                                 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1020                                         }
1021                                 }
1022                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1023                                 {
1024                                         if (cl_particles_quake.integer)
1025                                         {
1026                                                 dec = 6;
1027                                                 color = particlepalette[52 + (rand()&7)];
1028                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1029                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1030                                         }
1031                                         else if (gamemode == GAME_GOODVSBAD2)
1032                                         {
1033                                                 dec = 6;
1034                                                 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1035                                         }
1036                                         else
1037                                         {
1038                                                 color = particlepalette[20 + (rand()&7)];
1039                                                 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1040                                         }
1041                                 }
1042                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1043                                 {
1044                                         if (cl_particles_quake.integer)
1045                                         {
1046                                                 dec = 6;
1047                                                 color = particlepalette[230 + (rand()&7)];
1048                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1049                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1050                                         }
1051                                         else
1052                                         {
1053                                                 color = particlepalette[226 + (rand()&7)];
1054                                                 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1055                                         }
1056                                 }
1057                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1058                                 {
1059                                         if (cl_particles_quake.integer)
1060                                         {
1061                                                 color = particlepalette[152 + (rand()&3)];
1062                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
1063                                         }
1064                                         else if (gamemode == GAME_GOODVSBAD2)
1065                                         {
1066                                                 dec = 6;
1067                                                 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1068                                         }
1069                                         else if (gamemode == GAME_PRYDON)
1070                                         {
1071                                                 dec = 6;
1072                                                 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1073                                         }
1074                                         else
1075                                                 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1076                                 }
1077                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1078                                 {
1079                                         dec = 7;
1080                                         CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
1081                                 }
1082                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1083                                 {
1084                                         dec = 4;
1085                                         CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
1086                                 }
1087                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1088                                         CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1089                         }
1090                         if (bubbles)
1091                         {
1092                                 if (effectnameindex == EFFECT_TR_ROCKET)
1093                                         CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1094                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1095                                         CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1096                         }
1097                         // advance to next time and position
1098                         dec *= qd;
1099                         len -= dec;
1100                         VectorMA (pos, dec, dir, pos);
1101                 }
1102                 if (ent)
1103                         ent->persistent.trail_time = len;
1104         }
1105         else if (developer.integer >= 1)
1106                 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1107 }
1108
1109 // this is also called on point effects with spawndlight = true and
1110 // spawnparticles = true
1111 // it is called CL_ParticleTrail because most code does not want to supply
1112 // these parameters, only trail handling does
1113 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
1114 {
1115         vec3_t center;
1116         qboolean found = false;
1117         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1118                 return; // invalid effect index
1119         if (!particleeffectname[effectnameindex][0])
1120                 return; // no such effect
1121         VectorLerp(originmins, 0.5, originmaxs, center);
1122         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1123         {
1124                 int effectinfoindex;
1125                 int supercontents;
1126                 int tex;
1127                 particleeffectinfo_t *info;
1128                 vec3_t center;
1129                 vec3_t centervelocity;
1130                 vec3_t traildir;
1131                 vec3_t trailpos;
1132                 vec3_t rvec;
1133                 vec_t traillen;
1134                 vec_t trailstep;
1135                 qboolean underwater;
1136                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1137                 VectorLerp(originmins, 0.5, originmaxs, center);
1138                 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1139                 supercontents = CL_PointSuperContents(center);
1140                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1141                 VectorSubtract(originmaxs, originmins, traildir);
1142                 traillen = VectorLength(traildir);
1143                 VectorNormalize(traildir);
1144                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1145                 {
1146                         if (info->effectnameindex == effectnameindex)
1147                         {
1148                                 found = true;
1149                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1150                                         continue;
1151                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1152                                         continue;
1153
1154                                 // spawn a dlight if requested
1155                                 if (info->lightradiusstart > 0 && spawndlight)
1156                                 {
1157                                         matrix4x4_t tempmatrix;
1158                                         if (info->trailspacing > 0)
1159                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1160                                         else
1161                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1162                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1163                                         {
1164                                                 // light flash (explosion, etc)
1165                                                 // called when effect starts
1166                                                 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1167                                         }
1168                                         else
1169                                         {
1170                                                 // glowing entity
1171                                                 // called by CL_LinkNetworkEntity
1172                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1173                                                 R_RTLight_Update(&r_refdef.scene.lights[r_refdef.scene.numlights++], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1174                                         }
1175                                 }
1176
1177                                 if (!spawnparticles)
1178                                         continue;
1179
1180                                 // spawn particles
1181                                 tex = info->tex[0];
1182                                 if (info->tex[1] > info->tex[0])
1183                                 {
1184                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1185                                         tex = min(tex, info->tex[1] - 1);
1186                                 }
1187                                 if (info->particletype == pt_decal)
1188                                         CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1189                                 else if (info->particletype == pt_beam)
1190                                         CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
1191                                 else
1192                                 {
1193                                         if (!cl_particles.integer)
1194                                                 continue;
1195                                         switch (info->particletype)
1196                                         {
1197                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1198                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1199                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1200                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1201                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1202                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1203                                         default: break;
1204                                         }
1205                                         VectorCopy(originmins, trailpos);
1206                                         if (info->trailspacing > 0)
1207                                         {
1208                                                 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1209                                                 trailstep = info->trailspacing / cl_particles_quality.value;
1210                                         }
1211                                         else
1212                                         {
1213                                                 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1214                                                 trailstep = 0;
1215                                         }
1216                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1217                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1218                                         {
1219                                                 if (info->tex[1] > info->tex[0])
1220                                                 {
1221                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1222                                                         tex = min(tex, info->tex[1] - 1);
1223                                                 }
1224                                                 if (!trailstep)
1225                                                 {
1226                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1227                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1228                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1229                                                 }
1230                                                 VectorRandom(rvec);
1231                                                 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0);
1232                                                 if (trailstep)
1233                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1234                                         }
1235                                 }
1236                         }
1237                 }
1238         }
1239         if (!found)
1240                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1241 }
1242
1243 void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor)
1244 {
1245         CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1246 }
1247
1248 /*
1249 ===============
1250 CL_EntityParticles
1251 ===============
1252 */
1253 void CL_EntityParticles (const entity_t *ent)
1254 {
1255         int i;
1256         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1257         static vec3_t avelocities[NUMVERTEXNORMALS];
1258         if (!cl_particles.integer) return;
1259         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1260
1261         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1262
1263         if (!avelocities[0][0])
1264                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1265                         avelocities[0][i] = lhrandom(0, 2.55);
1266
1267         for (i = 0;i < NUMVERTEXNORMALS;i++)
1268         {
1269                 yaw = cl.time * avelocities[i][0];
1270                 pitch = cl.time * avelocities[i][1];
1271                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1272                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1273                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1274                 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0);
1275         }
1276 }
1277
1278
1279 void CL_ReadPointFile_f (void)
1280 {
1281         vec3_t org, leakorg;
1282         int r, c, s;
1283         char *pointfile = NULL, *pointfilepos, *t, tchar;
1284         char name[MAX_OSPATH];
1285
1286         if (!cl.worldmodel)
1287                 return;
1288
1289         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1290         strlcat (name, ".pts", sizeof (name));
1291         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1292         if (!pointfile)
1293         {
1294                 Con_Printf("Could not open %s\n", name);
1295                 return;
1296         }
1297
1298         Con_Printf("Reading %s...\n", name);
1299         VectorClear(leakorg);
1300         c = 0;
1301         s = 0;
1302         pointfilepos = pointfile;
1303         while (*pointfilepos)
1304         {
1305                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1306                         pointfilepos++;
1307                 if (!*pointfilepos)
1308                         break;
1309                 t = pointfilepos;
1310                 while (*t && *t != '\n' && *t != '\r')
1311                         t++;
1312                 tchar = *t;
1313                 *t = 0;
1314                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1315                 *t = tchar;
1316                 pointfilepos = t;
1317                 if (r != 3)
1318                         break;
1319                 if (c == 0)
1320                         VectorCopy(org, leakorg);
1321                 c++;
1322
1323                 if (cl.num_particles < cl.max_particles - 3)
1324                 {
1325                         s++;
1326                         CL_NewParticle(pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0);
1327                 }
1328         }
1329         Mem_Free(pointfile);
1330         VectorCopy(leakorg, org);
1331         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1332
1333         CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0);
1334         CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0);
1335         CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0);
1336 }
1337
1338 /*
1339 ===============
1340 CL_ParseParticleEffect
1341
1342 Parse an effect out of the server message
1343 ===============
1344 */
1345 void CL_ParseParticleEffect (void)
1346 {
1347         vec3_t org, dir;
1348         int i, count, msgcount, color;
1349
1350         MSG_ReadVector(org, cls.protocol);
1351         for (i=0 ; i<3 ; i++)
1352                 dir[i] = MSG_ReadChar ();
1353         msgcount = MSG_ReadByte ();
1354         color = MSG_ReadByte ();
1355
1356         if (msgcount == 255)
1357                 count = 1024;
1358         else
1359                 count = msgcount;
1360
1361         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1362 }
1363
1364 /*
1365 ===============
1366 CL_ParticleExplosion
1367
1368 ===============
1369 */
1370 void CL_ParticleExplosion (const vec3_t org)
1371 {
1372         int i;
1373         trace_t trace;
1374         //vec3_t v;
1375         //vec3_t v2;
1376         if (cl_stainmaps.integer)
1377                 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1378         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1379
1380         if (cl_particles_quake.integer)
1381         {
1382                 for (i = 0;i < 1024;i++)
1383                 {
1384                         int r, color;
1385                         r = rand()&3;
1386                         if (i & 1)
1387                         {
1388                                 color = particlepalette[ramp1[r]];
1389                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1390                         }
1391                         else
1392                         {
1393                                 color = particlepalette[ramp2[r]];
1394                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1395                         }
1396                 }
1397         }
1398         else
1399         {
1400                 i = CL_PointSuperContents(org);
1401                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1402                 {
1403                         if (cl_particles.integer && cl_particles_bubbles.integer)
1404                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1405                                         CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96);
1406                 }
1407                 else
1408                 {
1409                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1410                         {
1411                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1412                                 {
1413                                         int k;
1414                                         vec3_t v, v2;
1415                                         for (k = 0;k < 16;k++)
1416                                         {
1417                                                 VectorRandom(v2);
1418                                                 VectorMA(org, 128, v2, v);
1419                                                 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1420                                                 if (trace.fraction >= 0.1)
1421                                                         break;
1422                                         }
1423                                         VectorSubtract(trace.endpos, org, v2);
1424                                         VectorScale(v2, 2.0f, v2);
1425                                         CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1426                                 }
1427                         }
1428                 }
1429         }
1430
1431         if (cl_particles_explosions_shell.integer)
1432                 R_NewExplosion(org);
1433 }
1434
1435 /*
1436 ===============
1437 CL_ParticleExplosion2
1438
1439 ===============
1440 */
1441 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1442 {
1443         int i, k;
1444         if (!cl_particles.integer) return;
1445
1446         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1447         {
1448                 k = particlepalette[colorStart + (i % colorLength)];
1449                 if (cl_particles_quake.integer)
1450                         CL_NewParticle(pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
1451                 else
1452                         CL_NewParticle(pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192);
1453         }
1454 }
1455
1456 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1457 {
1458         if (cl_particles_sparks.integer)
1459         {
1460                 sparkcount *= cl_particles_quality.value;
1461                 while(sparkcount-- > 0)
1462                         CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64);
1463         }
1464 }
1465
1466 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1467 {
1468         if (cl_particles_smoke.integer)
1469         {
1470                 smokecount *= cl_particles_quality.value;
1471                 while(smokecount-- > 0)
1472                         CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0);
1473         }
1474 }
1475
1476 void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel)
1477 {
1478         int k;
1479         if (!cl_particles.integer) return;
1480
1481         count = (int)(count * cl_particles_quality.value);
1482         while (count--)
1483         {
1484                 k = particlepalette[colorbase + (rand()&3)];
1485                 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel);
1486         }
1487 }
1488
1489 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1490 {
1491         int k;
1492         float z, minz, maxz;
1493         if (!cl_particles.integer) return;
1494         if (dir[2] < 0) // falling
1495                 z = maxs[2];
1496         else // rising??
1497                 z = mins[2];
1498
1499         minz = z - fabs(dir[2]) * 0.1;
1500         maxz = z + fabs(dir[2]) * 0.1;
1501         minz = bound(mins[2], minz, maxs[2]);
1502         maxz = bound(mins[2], maxz, maxs[2]);
1503
1504         count = (int)(count * cl_particles_quality.value);
1505
1506         switch(type)
1507         {
1508         case 0:
1509                 if (!cl_particles_rain.integer) break;
1510                 count *= 4; // ick, this should be in the mod or maps?
1511
1512                 while(count--)
1513                 {
1514                         k = particlepalette[colorbase + (rand()&3)];
1515                         if (gamemode == GAME_GOODVSBAD2)
1516                                 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, 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, 0);
1517                         else
1518                                 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, 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, 0);
1519                 }
1520                 break;
1521         case 1:
1522                 if (!cl_particles_snow.integer) break;
1523                 while(count--)
1524                 {
1525                         k = particlepalette[colorbase + (rand()&3)];
1526                         if (gamemode == GAME_GOODVSBAD2)
1527                                 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, 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, 0);
1528                         else
1529                                 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, 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, 0);
1530                 }
1531                 break;
1532         default:
1533                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1534         }
1535 }
1536
1537 #define MAX_PARTICLETEXTURES 64
1538 // particletexture_t is a rectangle in the particlefonttexture
1539 typedef struct particletexture_s
1540 {
1541         rtexture_t *texture;
1542         float s1, t1, s2, t2;
1543 }
1544 particletexture_t;
1545
1546 static rtexturepool_t *particletexturepool;
1547 static rtexture_t *particlefonttexture;
1548 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1549
1550 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1551 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1552
1553 #define PARTICLETEXTURESIZE 64
1554 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1555
1556 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1557 {
1558         float dz, f, dot;
1559         vec3_t normal;
1560         dz = 1 - (dx*dx+dy*dy);
1561         if (dz > 0) // it does hit the sphere
1562         {
1563                 f = 0;
1564                 // back side
1565                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1566                 VectorNormalize(normal);
1567                 dot = DotProduct(normal, light);
1568                 if (dot > 0.5) // interior reflection
1569                         f += ((dot *  2) - 1);
1570                 else if (dot < -0.5) // exterior reflection
1571                         f += ((dot * -2) - 1);
1572                 // front side
1573                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1574                 VectorNormalize(normal);
1575                 dot = DotProduct(normal, light);
1576                 if (dot > 0.5) // interior reflection
1577                         f += ((dot *  2) - 1);
1578                 else if (dot < -0.5) // exterior reflection
1579                         f += ((dot * -2) - 1);
1580                 f *= 128;
1581                 f += 16; // just to give it a haze so you can see the outline
1582                 f = bound(0, f, 255);
1583                 return (unsigned char) f;
1584         }
1585         else
1586                 return 0;
1587 }
1588
1589 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1590 {
1591         int basex, basey, y;
1592         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1593         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1594         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1595                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1596 }
1597
1598 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1599 {
1600         int x, y;
1601         float cx, cy, dx, dy, f, iradius;
1602         unsigned char *d;
1603         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1604         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1605         iradius = 1.0f / radius;
1606         alpha *= (1.0f / 255.0f);
1607         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1608         {
1609                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1610                 {
1611                         dx = (x - cx);
1612                         dy = (y - cy);
1613                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1614                         if (f > 0)
1615                         {
1616                                 if (f > 1)
1617                                         f = 1;
1618                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1619                                 d[0] += (int)(f * (blue  - d[0]));
1620                                 d[1] += (int)(f * (green - d[1]));
1621                                 d[2] += (int)(f * (red   - d[2]));
1622                         }
1623                 }
1624         }
1625 }
1626
1627 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1628 {
1629         int i;
1630         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1631         {
1632                 data[0] = bound(minb, data[0], maxb);
1633                 data[1] = bound(ming, data[1], maxg);
1634                 data[2] = bound(minr, data[2], maxr);
1635         }
1636 }
1637
1638 void particletextureinvert(unsigned char *data)
1639 {
1640         int i;
1641         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1642         {
1643                 data[0] = 255 - data[0];
1644                 data[1] = 255 - data[1];
1645                 data[2] = 255 - data[2];
1646         }
1647 }
1648
1649 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1650 static void R_InitBloodTextures (unsigned char *particletexturedata)
1651 {
1652         int i, j, k, m;
1653         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1654
1655         // blood particles
1656         for (i = 0;i < 8;i++)
1657         {
1658                 memset(&data[0][0][0], 255, sizeof(data));
1659                 for (k = 0;k < 24;k++)
1660                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1661                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1662                 particletextureinvert(&data[0][0][0]);
1663                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1664         }
1665
1666         // blood decals
1667         for (i = 0;i < 8;i++)
1668         {
1669                 memset(&data[0][0][0], 255, sizeof(data));
1670                 m = 8;
1671                 for (j = 1;j < 10;j++)
1672                         for (k = min(j, m - 1);k < m;k++)
1673                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1674                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1675                 particletextureinvert(&data[0][0][0]);
1676                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1677         }
1678
1679 }
1680
1681 //uncomment this to make engine save out particle font to a tga file when run
1682 //#define DUMPPARTICLEFONT
1683
1684 static void R_InitParticleTexture (void)
1685 {
1686         int x, y, d, i, k, m;
1687         float dx, dy, f;
1688         vec3_t light;
1689
1690         // a note: decals need to modulate (multiply) the background color to
1691         // properly darken it (stain), and they need to be able to alpha fade,
1692         // this is a very difficult challenge because it means fading to white
1693         // (no change to background) rather than black (darkening everything
1694         // behind the whole decal polygon), and to accomplish this the texture is
1695         // inverted (dark red blood on white background becomes brilliant cyan
1696         // and white on black background) so we can alpha fade it to black, then
1697         // we invert it again during the blendfunc to make it work...
1698
1699 #ifndef DUMPPARTICLEFONT
1700         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1701         if (!particlefonttexture)
1702 #endif
1703         {
1704                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1705                 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1706                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1707
1708                 // smoke
1709                 for (i = 0;i < 8;i++)
1710                 {
1711                         memset(&data[0][0][0], 255, sizeof(data));
1712                         do
1713                         {
1714                                 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1715
1716                                 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1717                                 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1718                                 m = 0;
1719                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1720                                 {
1721                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1722                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1723                                         {
1724                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1725                                                 d = (noise2[y][x] - 128) * 3 + 192;
1726                                                 if (d > 0)
1727                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
1728                                                 d = (d * noise1[y][x]) >> 7;
1729                                                 d = bound(0, d, 255);
1730                                                 data[y][x][3] = (unsigned char) d;
1731                                                 if (m < d)
1732                                                         m = d;
1733                                         }
1734                                 }
1735                         }
1736                         while (m < 224);
1737                         setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1738                 }
1739
1740                 // rain splash
1741                 memset(&data[0][0][0], 255, sizeof(data));
1742                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1743                 {
1744                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1745                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1746                         {
1747                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1748                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1749                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1750                         }
1751                 }
1752                 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1753
1754                 // normal particle
1755                 memset(&data[0][0][0], 255, sizeof(data));
1756                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1757                 {
1758                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1759                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1760                         {
1761                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1762                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1763                                 d = bound(0, d, 255);
1764                                 data[y][x][3] = (unsigned char) d;
1765                         }
1766                 }
1767                 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1768
1769                 // rain
1770                 memset(&data[0][0][0], 255, sizeof(data));
1771                 light[0] = 1;light[1] = 1;light[2] = 1;
1772                 VectorNormalize(light);
1773                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1774                 {
1775                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1776                         // stretch upper half of bubble by +50% and shrink lower half by -50%
1777                         // (this gives an elongated teardrop shape)
1778                         if (dy > 0.5f)
1779                                 dy = (dy - 0.5f) * 2.0f;
1780                         else
1781                                 dy = (dy - 0.5f) / 1.5f;
1782                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1783                         {
1784                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1785                                 // shrink bubble width to half
1786                                 dx *= 2.0f;
1787                                 data[y][x][3] = shadebubble(dx, dy, light);
1788                         }
1789                 }
1790                 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1791
1792                 // bubble
1793                 memset(&data[0][0][0], 255, sizeof(data));
1794                 light[0] = 1;light[1] = 1;light[2] = 1;
1795                 VectorNormalize(light);
1796                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1797                 {
1798                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1799                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1800                         {
1801                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1802                                 data[y][x][3] = shadebubble(dx, dy, light);
1803                         }
1804                 }
1805                 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1806
1807                 // Blood particles and blood decals
1808                 R_InitBloodTextures (particletexturedata);
1809
1810                 // bullet decals
1811                 for (i = 0;i < 8;i++)
1812                 {
1813                         memset(&data[0][0][0], 255, sizeof(data));
1814                         for (k = 0;k < 12;k++)
1815                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1816                         for (k = 0;k < 3;k++)
1817                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1818                         //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1819                         particletextureinvert(&data[0][0][0]);
1820                         setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1821                 }
1822
1823 #ifdef DUMPPARTICLEFONT
1824                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1825 #endif
1826
1827                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1828
1829                 Mem_Free(particletexturedata);
1830         }
1831         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1832         {
1833                 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1834                 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1835                 particletexture[i].texture = particlefonttexture;
1836                 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1837                 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1838                 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1839                 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1840         }
1841
1842 #ifndef DUMPPARTICLEFONT
1843         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1844         if (!particletexture[tex_beam].texture)
1845 #endif
1846         {
1847                 unsigned char noise3[64][64], data2[64][16][4];
1848                 // nexbeam
1849                 fractalnoise(&noise3[0][0], 64, 4);
1850                 m = 0;
1851                 for (y = 0;y < 64;y++)
1852                 {
1853                         dy = (y - 0.5f*64) / (64*0.5f-1);
1854                         for (x = 0;x < 16;x++)
1855                         {
1856                                 dx = (x - 0.5f*16) / (16*0.5f-2);
1857                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1858                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1859                                 data2[y][x][3] = 255;
1860                         }
1861                 }
1862
1863 #ifdef DUMPPARTICLEFONT
1864                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1865 #endif
1866                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1867         }
1868         particletexture[tex_beam].s1 = 0;
1869         particletexture[tex_beam].t1 = 0;
1870         particletexture[tex_beam].s2 = 1;
1871         particletexture[tex_beam].t2 = 1;
1872 }
1873
1874 static void r_part_start(void)
1875 {
1876         particletexturepool = R_AllocTexturePool();
1877         R_InitParticleTexture ();
1878         CL_Particles_LoadEffectInfo();
1879 }
1880
1881 static void r_part_shutdown(void)
1882 {
1883         R_FreeTexturePool(&particletexturepool);
1884 }
1885
1886 static void r_part_newmap(void)
1887 {
1888 }
1889
1890 #define BATCHSIZE 256
1891 int particle_element3i[BATCHSIZE*6];
1892
1893 void R_Particles_Init (void)
1894 {
1895         int i;
1896         for (i = 0;i < BATCHSIZE;i++)
1897         {
1898                 particle_element3i[i*6+0] = i*4+0;
1899                 particle_element3i[i*6+1] = i*4+1;
1900                 particle_element3i[i*6+2] = i*4+2;
1901                 particle_element3i[i*6+3] = i*4+0;
1902                 particle_element3i[i*6+4] = i*4+2;
1903                 particle_element3i[i*6+5] = i*4+3;
1904         }
1905
1906         Cvar_RegisterVariable(&r_drawparticles);
1907         Cvar_RegisterVariable(&r_drawdecals);
1908         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1909 }
1910
1911 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1912 {
1913         int surfacelistindex;
1914         const decal_t *d;
1915         float *v3f, *t2f, *c4f;
1916         particletexture_t *tex;
1917         float right[3], up[3], size, ca;
1918         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1919         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1920
1921         r_refdef.stats.decals += numsurfaces;
1922         R_Mesh_Matrix(&identitymatrix);
1923         R_Mesh_ResetTextureState();
1924         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1925         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1926         R_Mesh_ColorPointer(particle_color4f, 0, 0);
1927         GL_DepthMask(false);
1928         GL_DepthRange(0, 1);
1929         GL_PolygonOffset(0, 0);
1930         GL_DepthTest(true);
1931         GL_CullFace(GL_NONE);
1932
1933         // generate all the vertices at once
1934         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1935         {
1936                 d = cl.decals + surfacelist[surfacelistindex];
1937
1938                 // calculate color
1939                 c4f = particle_color4f + 16*surfacelistindex;
1940                 ca = d->alpha * alphascale;
1941                 if (r_refdef.fogenabled)
1942                         ca *= FogPoint_World(d->org);
1943                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1944                 Vector4Copy(c4f, c4f + 4);
1945                 Vector4Copy(c4f, c4f + 8);
1946                 Vector4Copy(c4f, c4f + 12);
1947
1948                 // calculate vertex positions
1949                 size = d->size * cl_particles_size.value;
1950                 VectorVectors(d->normal, right, up);
1951                 VectorScale(right, size, right);
1952                 VectorScale(up, size, up);
1953                 v3f = particle_vertex3f + 12*surfacelistindex;
1954                 v3f[ 0] = d->org[0] - right[0] - up[0];
1955                 v3f[ 1] = d->org[1] - right[1] - up[1];
1956                 v3f[ 2] = d->org[2] - right[2] - up[2];
1957                 v3f[ 3] = d->org[0] - right[0] + up[0];
1958                 v3f[ 4] = d->org[1] - right[1] + up[1];
1959                 v3f[ 5] = d->org[2] - right[2] + up[2];
1960                 v3f[ 6] = d->org[0] + right[0] + up[0];
1961                 v3f[ 7] = d->org[1] + right[1] + up[1];
1962                 v3f[ 8] = d->org[2] + right[2] + up[2];
1963                 v3f[ 9] = d->org[0] + right[0] - up[0];
1964                 v3f[10] = d->org[1] + right[1] - up[1];
1965                 v3f[11] = d->org[2] + right[2] - up[2];
1966
1967                 // calculate texcoords
1968                 tex = &particletexture[d->texnum];
1969                 t2f = particle_texcoord2f + 8*surfacelistindex;
1970                 t2f[0] = tex->s1;t2f[1] = tex->t2;
1971                 t2f[2] = tex->s1;t2f[3] = tex->t1;
1972                 t2f[4] = tex->s2;t2f[5] = tex->t1;
1973                 t2f[6] = tex->s2;t2f[7] = tex->t2;
1974         }
1975
1976         // now render the decals all at once
1977         // (this assumes they all use one particle font texture!)
1978         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1979         R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
1980         GL_LockArrays(0, numsurfaces*4);
1981         R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
1982         GL_LockArrays(0, 0);
1983 }
1984
1985 void R_DrawDecals (void)
1986 {
1987         int i;
1988         decal_t *decal;
1989         float frametime;
1990         float decalfade;
1991
1992         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
1993         cl.decals_updatetime += frametime;
1994
1995         // LordHavoc: early out conditions
1996         if ((!cl.num_decals) || (!r_drawdecals.integer))
1997                 return;
1998
1999         decalfade = frametime * 256 / cl_decals_fadetime.value;
2000
2001         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2002         {
2003                 if (!decal->typeindex)
2004                         continue;
2005
2006                 if (cl.time > decal->time2 + cl_decals_time.value)
2007                 {
2008                         decal->alpha -= decalfade;
2009                         if (decal->alpha <= 0)
2010                                 goto killdecal;
2011                 }
2012
2013                 if (decal->owner)
2014                 {
2015                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2016                         {
2017                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2018                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2019                         }
2020                         else
2021                                 goto killdecal;
2022                 }
2023                 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2024                 continue;
2025 killdecal:
2026                 decal->typeindex = 0;
2027                 if (cl.free_decal > i)
2028                         cl.free_decal = i;
2029         }
2030
2031         // reduce cl.num_decals if possible
2032         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2033                 cl.num_decals--;
2034 }
2035
2036 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2037 {
2038         int surfacelistindex;
2039         int batchstart, batchcount;
2040         const particle_t *p;
2041         pblend_t blendmode;
2042         rtexture_t *texture;
2043         float *v3f, *t2f, *c4f;
2044         particletexture_t *tex;
2045         float up2[3], v[3], right[3], up[3], fog, ifog, size;
2046         float ambient[3], diffuse[3], diffusenormal[3];
2047         vec4_t colormultiplier;
2048         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2049
2050         Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2051
2052         r_refdef.stats.particles += numsurfaces;
2053         R_Mesh_Matrix(&identitymatrix);
2054         R_Mesh_ResetTextureState();
2055         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2056         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2057         R_Mesh_ColorPointer(particle_color4f, 0, 0);
2058         GL_DepthMask(false);
2059         GL_DepthRange(0, 1);
2060         GL_PolygonOffset(0, 0);
2061         GL_DepthTest(true);
2062         GL_CullFace(GL_NONE);
2063
2064         // first generate all the vertices at once
2065         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2066         {
2067                 p = cl.particles + surfacelist[surfacelistindex];
2068
2069                 blendmode = particletype[p->typeindex].blendmode;
2070
2071                 c4f[0] = p->color[0] * colormultiplier[0];
2072                 c4f[1] = p->color[1] * colormultiplier[1];
2073                 c4f[2] = p->color[2] * colormultiplier[2];
2074                 c4f[3] = p->alpha * colormultiplier[3];
2075                 switch (blendmode)
2076                 {
2077                 case PBLEND_MOD:
2078                 case PBLEND_ADD:
2079                         // additive and modulate can just fade out in fog (this is correct)
2080                         if (r_refdef.fogenabled)
2081                                 c4f[3] *= FogPoint_World(p->org);
2082                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2083                         c4f[0] *= c4f[3];
2084                         c4f[1] *= c4f[3];
2085                         c4f[2] *= c4f[3];
2086                         c4f[3] = 1;
2087                         break;
2088                 case PBLEND_ALPHA:
2089                         // note: lighting is not cheap!
2090                         if (particletype[p->typeindex].lighting)
2091                         {
2092                                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2093                                 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2094                                 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2095                                 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2096                         }
2097                         // mix in the fog color
2098                         if (r_refdef.fogenabled)
2099                         {
2100                                 fog = FogPoint_World(p->org);
2101                                 ifog = 1 - fog;
2102                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2103                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2104                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2105                         }
2106                         break;
2107                 }
2108                 // copy the color into the other three vertices
2109                 Vector4Copy(c4f, c4f + 4);
2110                 Vector4Copy(c4f, c4f + 8);
2111                 Vector4Copy(c4f, c4f + 12);
2112
2113                 size = p->size * cl_particles_size.value;
2114                 tex = &particletexture[p->texnum];
2115                 switch(particletype[p->typeindex].orientation)
2116                 {
2117                 case PARTICLE_BILLBOARD:
2118                         VectorScale(r_refdef.view.left, -size, right);
2119                         VectorScale(r_refdef.view.up, size, up);
2120                         v3f[ 0] = p->org[0] - right[0] - up[0];
2121                         v3f[ 1] = p->org[1] - right[1] - up[1];
2122                         v3f[ 2] = p->org[2] - right[2] - up[2];
2123                         v3f[ 3] = p->org[0] - right[0] + up[0];
2124                         v3f[ 4] = p->org[1] - right[1] + up[1];
2125                         v3f[ 5] = p->org[2] - right[2] + up[2];
2126                         v3f[ 6] = p->org[0] + right[0] + up[0];
2127                         v3f[ 7] = p->org[1] + right[1] + up[1];
2128                         v3f[ 8] = p->org[2] + right[2] + up[2];
2129                         v3f[ 9] = p->org[0] + right[0] - up[0];
2130                         v3f[10] = p->org[1] + right[1] - up[1];
2131                         v3f[11] = p->org[2] + right[2] - up[2];
2132                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2133                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2134                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2135                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2136                         break;
2137                 case PARTICLE_ORIENTED_DOUBLESIDED:
2138                         VectorVectors(p->vel, right, up);
2139                         VectorScale(right, size, right);
2140                         VectorScale(up, size, up);
2141                         v3f[ 0] = p->org[0] - right[0] - up[0];
2142                         v3f[ 1] = p->org[1] - right[1] - up[1];
2143                         v3f[ 2] = p->org[2] - right[2] - up[2];
2144                         v3f[ 3] = p->org[0] - right[0] + up[0];
2145                         v3f[ 4] = p->org[1] - right[1] + up[1];
2146                         v3f[ 5] = p->org[2] - right[2] + up[2];
2147                         v3f[ 6] = p->org[0] + right[0] + up[0];
2148                         v3f[ 7] = p->org[1] + right[1] + up[1];
2149                         v3f[ 8] = p->org[2] + right[2] + up[2];
2150                         v3f[ 9] = p->org[0] + right[0] - up[0];
2151                         v3f[10] = p->org[1] + right[1] - up[1];
2152                         v3f[11] = p->org[2] + right[2] - up[2];
2153                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2154                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2155                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2156                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2157                         break;
2158                 case PARTICLE_SPARK:
2159                         VectorMA(p->org, -0.02, p->vel, v);
2160                         VectorMA(p->org, 0.02, p->vel, up2);
2161                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2162                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2163                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2164                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2165                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2166                         break;
2167                 case PARTICLE_BEAM:
2168                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2169                         VectorSubtract(p->vel, p->org, up);
2170                         VectorNormalize(up);
2171                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2172                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2173                         t2f[0] = 1;t2f[1] = v[0];
2174                         t2f[2] = 0;t2f[3] = v[0];
2175                         t2f[4] = 0;t2f[5] = v[1];
2176                         t2f[6] = 1;t2f[7] = v[1];
2177                         break;
2178                 }
2179         }
2180
2181         // now render batches of particles based on blendmode and texture
2182         blendmode = -1;
2183         texture = NULL;
2184         GL_LockArrays(0, numsurfaces*4);
2185         batchstart = 0;
2186         batchcount = 0;
2187         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2188         {
2189                 p = cl.particles + surfacelist[surfacelistindex];
2190
2191                 if (blendmode != particletype[p->typeindex].blendmode)
2192                 {
2193                         blendmode = particletype[p->typeindex].blendmode;
2194                         switch(blendmode)
2195                         {
2196                         case PBLEND_ALPHA:
2197                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2198                                 break;
2199                         case PBLEND_ADD:
2200                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2201                                 break;
2202                         case PBLEND_MOD:
2203                                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2204                                 break;
2205                         }
2206                 }
2207                 if (texture != particletexture[p->texnum].texture)
2208                 {
2209                         texture = particletexture[p->texnum].texture;
2210                         R_Mesh_TexBind(0, R_GetTexture(texture));
2211                 }
2212
2213                 // iterate until we find a change in settings
2214                 batchstart = surfacelistindex++;
2215                 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2216                 {
2217                         p = cl.particles + surfacelist[surfacelistindex];
2218                         if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2219                                 break;
2220                 }
2221
2222                 batchcount = surfacelistindex - batchstart;
2223                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2224         }
2225         GL_LockArrays(0, 0);
2226 }
2227
2228 void R_DrawParticles (void)
2229 {
2230         int i, j, a, content;
2231         float minparticledist;
2232         particle_t *p;
2233         float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2234         int hitent;
2235         trace_t trace;
2236         qboolean update;
2237
2238         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2239         cl.particles_updatetime += frametime;
2240
2241         // LordHavoc: early out conditions
2242         if ((!cl.num_particles) || (!r_drawparticles.integer))
2243                 return;
2244
2245         minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2246         gravity = frametime * cl.movevars_gravity;
2247         dvel = 1+4*frametime;
2248         decalfade = frametime * 255 / cl_decals_fadetime.value;
2249         update = frametime > 0;
2250
2251         j = 0;
2252         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2253         {
2254                 if (!p->typeindex)
2255                         continue;
2256
2257                 if (update)
2258                 {
2259                         if (p->delayedspawn > cl.time)
2260                                 continue;
2261                         p->delayedspawn = 0;
2262
2263                         content = 0;
2264
2265                         p->size += p->sizeincrease * frametime;
2266                         p->alpha -= p->alphafade * frametime;
2267
2268                         if (p->alpha <= 0 || p->die <= cl.time)
2269                                 goto killparticle;
2270
2271                         if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2272                         {
2273                                 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2274                                 {
2275                                         if (p->typeindex == pt_blood)
2276                                                 p->size += frametime * 8;
2277                                         else
2278                                                 p->vel[2] -= p->gravity * gravity;
2279                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2280                                         VectorScale(p->vel, f, p->vel);
2281                                 }
2282                                 else
2283                                 {
2284                                         p->vel[2] -= p->gravity * gravity;
2285                                         if (p->airfriction)
2286                                         {
2287                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2288                                                 VectorScale(p->vel, f, p->vel);
2289                                         }
2290                                 }
2291
2292                                 VectorCopy(p->org, oldorg);
2293                                 VectorMA(p->org, frametime, p->vel, p->org);
2294                                 if (p->bounce && cl.time >= p->delayedcollisions)
2295                                 {
2296                                         trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2297                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2298                                         // or if the trace hit something flagged as NOIMPACT
2299                                         // then remove the particle
2300                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2301                                                 goto killparticle;
2302                                         VectorCopy(trace.endpos, p->org);
2303                                         // react if the particle hit something
2304                                         if (trace.fraction < 1)
2305                                         {
2306                                                 VectorCopy(trace.endpos, p->org);
2307                                                 if (p->typeindex == pt_rain)
2308                                                 {
2309                                                         // raindrop - splash on solid/water/slime/lava
2310                                                         int count;
2311                                                         // convert from a raindrop particle to a rainsplash decal
2312                                                         VectorCopy(trace.plane.normal, p->vel);
2313                                                         VectorAdd(p->org, p->vel, p->org);
2314                                                         p->typeindex = pt_raindecal;
2315                                                         p->texnum = tex_rainsplash;
2316                                                         p->time2 = cl.time;
2317                                                         p->alphafade = p->alpha / 0.4;
2318                                                         p->bounce = 0;
2319                                                         p->airfriction = 0;
2320                                                         p->liquidfriction = 0;
2321                                                         p->gravity = 0;
2322                                                         p->size *= 1.0f;
2323                                                         p->sizeincrease = p->size * 20;
2324                                                         count = (int)lhrandom(1, 10);
2325                                                         while(count--)
2326                                                                 CL_NewParticle(pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
2327                                                         continue;
2328                                                 }
2329                                                 else if (p->typeindex == pt_blood)
2330                                                 {
2331                                                         // blood - splash on solid
2332                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2333                                                                 goto killparticle;
2334                                                         if (cl_stainmaps.integer)
2335                                                                 R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
2336                                                         if (cl_decals.integer)
2337                                                         {
2338                                                                 // create a decal for the blood splat
2339                                                                 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2340                                                         }
2341                                                         goto killparticle;
2342                                                 }
2343                                                 else if (p->bounce < 0)
2344                                                 {
2345                                                         // bounce -1 means remove on impact
2346                                                         goto killparticle;
2347                                                 }
2348                                                 else
2349                                                 {
2350                                                         // anything else - bounce off solid
2351                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2352                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2353                                                         if (DotProduct(p->vel, p->vel) < 0.03)
2354                                                                 VectorClear(p->vel);
2355                                                 }
2356                                         }
2357                                 }
2358                         }
2359
2360                         if (p->typeindex != pt_static)
2361                         {
2362                                 switch (p->typeindex)
2363                                 {
2364                                 case pt_entityparticle:
2365                                         // particle that removes itself after one rendered frame
2366                                         if (p->time2)
2367                                                 goto killparticle;
2368                                         else
2369                                                 p->time2 = 1;
2370                                         break;
2371                                 case pt_blood:
2372                                         a = CL_PointSuperContents(p->org);
2373                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2374                                                 goto killparticle;
2375                                         break;
2376                                 case pt_bubble:
2377                                         a = CL_PointSuperContents(p->org);
2378                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2379                                                 goto killparticle;
2380                                         break;
2381                                 case pt_rain:
2382                                         a = CL_PointSuperContents(p->org);
2383                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2384                                                 goto killparticle;
2385                                         break;
2386                                 case pt_snow:
2387                                         if (cl.time > p->time2)
2388                                         {
2389                                                 // snow flutter
2390                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
2391                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2392                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2393                                         }
2394                                         a = CL_PointSuperContents(p->org);
2395                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2396                                                 goto killparticle;
2397                                         break;
2398                                 default:
2399                                         break;
2400                                 }
2401                         }
2402                 }
2403                 else if (p->delayedspawn)
2404                         continue;
2405
2406                 // don't render particles too close to the view (they chew fillrate)
2407                 // also don't render particles behind the view (useless)
2408                 // further checks to cull to the frustum would be too slow here
2409                 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM)
2410                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2411
2412                 continue;
2413 killparticle:
2414                 p->typeindex = 0;
2415                 if (cl.free_particle > i)
2416                         cl.free_particle = i;
2417         }
2418
2419         // reduce cl.num_particles if possible
2420         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2421                 cl.num_particles--;
2422 }