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