]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
fix support for time command in effectinfo.txt - it was not being passed
[xonotic/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #include "cl_collision.h"
24 #include "image.h"
25 #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 & ~7) + (rand()&7)];
633                                         CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 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, lhrandom(0.1, 0.5));
634                                 }
635                         }
636                 }
637         }
638         else if (effectnameindex == EFFECT_TE_WIZSPIKE)
639                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
640         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
641                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
642         else if (effectnameindex == EFFECT_TE_SPIKE)
643         {
644                 if (cl_particles_bulletimpacts.integer)
645                 {
646                         if (cl_particles_quake.integer)
647                         {
648                                 if (cl_particles_smoke.integer)
649                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
650                         }
651                         else
652                         {
653                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
654                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
655                                 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);
656                         }
657                 }
658                 // bullet hole
659                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
660                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
661         }
662         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
663         {
664                 if (cl_particles_bulletimpacts.integer)
665                 {
666                         if (cl_particles_quake.integer)
667                         {
668                                 if (cl_particles_smoke.integer)
669                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
670                         }
671                         else
672                         {
673                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
674                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
675                                 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);
676                         }
677                 }
678                 // bullet hole
679                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
680                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
681                 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);
682         }
683         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
684         {
685                 if (cl_particles_bulletimpacts.integer)
686                 {
687                         if (cl_particles_quake.integer)
688                         {
689                                 if (cl_particles_smoke.integer)
690                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
691                         }
692                         else
693                         {
694                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
695                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
696                                 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);
697                         }
698                 }
699                 // bullet hole
700                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
701                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
702         }
703         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
704         {
705                 if (cl_particles_bulletimpacts.integer)
706                 {
707                         if (cl_particles_quake.integer)
708                         {
709                                 if (cl_particles_smoke.integer)
710                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
711                         }
712                         else
713                         {
714                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
715                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
716                                 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);
717                         }
718                 }
719                 // bullet hole
720                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
721                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
722                 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);
723         }
724         else if (effectnameindex == EFFECT_TE_BLOOD)
725         {
726                 if (!cl_particles_blood.integer)
727                         return;
728                 if (cl_particles_quake.integer)
729                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
730                 else
731                 {
732                         static double bloodaccumulator = 0;
733                         //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);
734                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
735                         for (;bloodaccumulator > 0;bloodaccumulator--)
736                                 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);
737                 }
738         }
739         else if (effectnameindex == EFFECT_TE_SPARK)
740                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
741         else if (effectnameindex == EFFECT_TE_PLASMABURN)
742         {
743                 // plasma scorch mark
744                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
745                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
746                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
747         }
748         else if (effectnameindex == EFFECT_TE_GUNSHOT)
749         {
750                 if (cl_particles_bulletimpacts.integer)
751                 {
752                         if (cl_particles_quake.integer)
753                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
754                         else
755                         {
756                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
757                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
758                                 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);
759                         }
760                 }
761                 // bullet hole
762                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
763                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
764         }
765         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
766         {
767                 if (cl_particles_bulletimpacts.integer)
768                 {
769                         if (cl_particles_quake.integer)
770                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
771                         else
772                         {
773                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
774                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
775                                 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);
776                         }
777                 }
778                 // bullet hole
779                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
780                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
781                 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);
782         }
783         else if (effectnameindex == EFFECT_TE_EXPLOSION)
784         {
785                 CL_ParticleExplosion(center);
786                 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);
787         }
788         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
789         {
790                 CL_ParticleExplosion(center);
791                 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);
792         }
793         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
794         {
795                 if (cl_particles_quake.integer)
796                 {
797                         int i;
798                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
799                         {
800                                 if (i & 1)
801                                         CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0);
802                                 else
803                                         CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0);
804                         }
805                 }
806                 else
807                         CL_ParticleExplosion(center);
808                 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);
809         }
810         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
811                 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);
812         else if (effectnameindex == EFFECT_TE_FLAMEJET)
813         {
814                 count *= cl_particles_quality.value;
815                 while (count-- > 0)
816                         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);
817         }
818         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
819         {
820                 float i, j, inc, vel;
821                 vec3_t dir, org;
822
823                 inc = 8 / cl_particles_quality.value;
824                 for (i = -128;i < 128;i += inc)
825                 {
826                         for (j = -128;j < 128;j += inc)
827                         {
828                                 dir[0] = j + lhrandom(0, inc);
829                                 dir[1] = i + lhrandom(0, inc);
830                                 dir[2] = 256;
831                                 org[0] = center[0] + dir[0];
832                                 org[1] = center[1] + dir[1];
833                                 org[2] = center[2] + lhrandom(0, 64);
834                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
835                                 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62));
836                         }
837                 }
838         }
839         else if (effectnameindex == EFFECT_TE_TELEPORT)
840         {
841                 float i, j, k, inc, vel;
842                 vec3_t dir;
843
844                 if (cl_particles_quake.integer)
845                         inc = 4 / cl_particles_quality.value;
846                 else
847                         inc = 8 / cl_particles_quality.value;
848                 for (i = -16;i < 16;i += inc)
849                 {
850                         for (j = -16;j < 16;j += inc)
851                         {
852                                 for (k = -24;k < 32;k += inc)
853                                 {
854                                         VectorSet(dir, i*8, j*8, k*8);
855                                         VectorNormalize(dir);
856                                         vel = lhrandom(50, 113);
857                                         if (cl_particles_quake.integer)
858                                                 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 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, lhrandom(0.2, 0.34));
859                                         else
860                                                 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);
861                                 }
862                         }
863                 }
864                 if (!cl_particles_quake.integer)
865                         CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0);
866                 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);
867         }
868         else if (effectnameindex == EFFECT_TE_TEI_G3)
869                 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);
870         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
871         {
872                 if (cl_particles_smoke.integer)
873                 {
874                         count *= 0.25f * cl_particles_quality.value;
875                         while (count-- > 0)
876                                 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);
877                 }
878         }
879         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
880         {
881                 CL_ParticleExplosion(center);
882                 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);
883         }
884         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
885         {
886                 float f;
887                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
888                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
889                 if (cl_particles_smoke.integer)
890                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
891                                 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);
892                 if (cl_particles_sparks.integer)
893                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
894                                 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);
895                 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);
896         }
897         else if (effectnameindex == EFFECT_EF_FLAME)
898         {
899                 count *= 300 * cl_particles_quality.value;
900                 while (count-- > 0)
901                         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);
902                 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);
903         }
904         else if (effectnameindex == EFFECT_EF_STARDUST)
905         {
906                 count *= 200 * cl_particles_quality.value;
907                 while (count-- > 0)
908                         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);
909                 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);
910         }
911         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
912         {
913                 vec3_t dir, pos;
914                 float len, dec, qd;
915                 int smoke, blood, bubbles, r, color;
916
917                 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
918                 {
919                         vec4_t light;
920                         Vector4Set(light, 0, 0, 0, 0);
921
922                         if (effectnameindex == EFFECT_TR_ROCKET)
923                                 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
924                         else if (effectnameindex == EFFECT_TR_VORESPIKE)
925                         {
926                                 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
927                                         Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
928                                 else
929                                         Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
930                         }
931                         else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
932                                 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
933
934                         if (light[3])
935                         {
936                                 matrix4x4_t tempmatrix;
937                                 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
938                                 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);
939                         }
940                 }
941
942                 if (!spawnparticles)
943                         return;
944
945                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
946                         return;
947
948                 VectorSubtract(originmaxs, originmins, dir);
949                 len = VectorNormalizeLength(dir);
950                 if (ent)
951                 {
952                         dec = -ent->persistent.trail_time;
953                         ent->persistent.trail_time += len;
954                         if (ent->persistent.trail_time < 0.01f)
955                                 return;
956
957                         // if we skip out, leave it reset
958                         ent->persistent.trail_time = 0.0f;
959                 }
960                 else
961                         dec = 0;
962
963                 // advance into this frame to reach the first puff location
964                 VectorMA(originmins, dec, dir, pos);
965                 len -= dec;
966
967                 smoke = cl_particles.integer && cl_particles_smoke.integer;
968                 blood = cl_particles.integer && cl_particles_blood.integer;
969                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
970                 qd = 1.0f / cl_particles_quality.value;
971
972                 while (len >= 0)
973                 {
974                         dec = 3;
975                         if (blood)
976                         {
977                                 if (effectnameindex == EFFECT_TR_BLOOD)
978                                 {
979                                         if (cl_particles_quake.integer)
980                                         {
981                                                 color = particlepalette[67 + (rand()&3)];
982                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2);
983                                         }
984                                         else
985                                         {
986                                                 dec = 16;
987                                                 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);
988                                         }
989                                 }
990                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
991                                 {
992                                         if (cl_particles_quake.integer)
993                                         {
994                                                 dec = 6;
995                                                 color = particlepalette[67 + (rand()&3)];
996                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2);
997                                         }
998                                         else
999                                         {
1000                                                 dec = 32;
1001                                                 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);
1002                                         }
1003                                 }
1004                         }
1005                         if (smoke)
1006                         {
1007                                 if (effectnameindex == EFFECT_TR_ROCKET)
1008                                 {
1009                                         if (cl_particles_quake.integer)
1010                                         {
1011                                                 r = rand()&3;
1012                                                 color = particlepalette[ramp3[r]];
1013                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r));
1014                                         }
1015                                         else
1016                                         {
1017                                                 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);
1018                                                 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);
1019                                         }
1020                                 }
1021                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1022                                 {
1023                                         if (cl_particles_quake.integer)
1024                                         {
1025                                                 r = 2 + (rand()%5);
1026                                                 color = particlepalette[ramp3[r]];
1027                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r));
1028                                         }
1029                                         else
1030                                         {
1031                                                 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);
1032                                         }
1033                                 }
1034                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1035                                 {
1036                                         if (cl_particles_quake.integer)
1037                                         {
1038                                                 dec = 6;
1039                                                 color = particlepalette[52 + (rand()&7)];
1040                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5);
1041                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5);
1042                                         }
1043                                         else if (gamemode == GAME_GOODVSBAD2)
1044                                         {
1045                                                 dec = 6;
1046                                                 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);
1047                                         }
1048                                         else
1049                                         {
1050                                                 color = particlepalette[20 + (rand()&7)];
1051                                                 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);
1052                                         }
1053                                 }
1054                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1055                                 {
1056                                         if (cl_particles_quake.integer)
1057                                         {
1058                                                 dec = 6;
1059                                                 color = particlepalette[230 + (rand()&7)];
1060                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5);
1061                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5);
1062                                         }
1063                                         else
1064                                         {
1065                                                 color = particlepalette[226 + (rand()&7)];
1066                                                 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);
1067                                         }
1068                                 }
1069                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1070                                 {
1071                                         if (cl_particles_quake.integer)
1072                                         {
1073                                                 color = particlepalette[152 + (rand()&3)];
1074                                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3);
1075                                         }
1076                                         else if (gamemode == GAME_GOODVSBAD2)
1077                                         {
1078                                                 dec = 6;
1079                                                 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);
1080                                         }
1081                                         else if (gamemode == GAME_PRYDON)
1082                                         {
1083                                                 dec = 6;
1084                                                 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);
1085                                         }
1086                                         else
1087                                                 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);
1088                                 }
1089                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1090                                 {
1091                                         dec = 7;
1092                                         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);
1093                                 }
1094                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1095                                 {
1096                                         dec = 4;
1097                                         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);
1098                                 }
1099                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1100                                         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);
1101                         }
1102                         if (bubbles)
1103                         {
1104                                 if (effectnameindex == EFFECT_TR_ROCKET)
1105                                         CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0);
1106                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1107                                         CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0);
1108                         }
1109                         // advance to next time and position
1110                         dec *= qd;
1111                         len -= dec;
1112                         VectorMA (pos, dec, dir, pos);
1113                 }
1114                 if (ent)
1115                         ent->persistent.trail_time = len;
1116         }
1117         else if (developer.integer >= 1)
1118                 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1119 }
1120
1121 // this is also called on point effects with spawndlight = true and
1122 // spawnparticles = true
1123 // it is called CL_ParticleTrail because most code does not want to supply
1124 // these parameters, only trail handling does
1125 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)
1126 {
1127         vec3_t center;
1128         qboolean found = false;
1129         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1130         {
1131                 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1132                 return; // no such effect
1133         }
1134         VectorLerp(originmins, 0.5, originmaxs, center);
1135         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1136         {
1137                 int effectinfoindex;
1138                 int supercontents;
1139                 int tex;
1140                 particleeffectinfo_t *info;
1141                 vec3_t center;
1142                 vec3_t centervelocity;
1143                 vec3_t traildir;
1144                 vec3_t trailpos;
1145                 vec3_t rvec;
1146                 vec_t traillen;
1147                 vec_t trailstep;
1148                 qboolean underwater;
1149                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1150                 VectorLerp(originmins, 0.5, originmaxs, center);
1151                 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1152                 supercontents = CL_PointSuperContents(center);
1153                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1154                 VectorSubtract(originmaxs, originmins, traildir);
1155                 traillen = VectorLength(traildir);
1156                 VectorNormalize(traildir);
1157                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1158                 {
1159                         if (info->effectnameindex == effectnameindex)
1160                         {
1161                                 found = true;
1162                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1163                                         continue;
1164                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1165                                         continue;
1166
1167                                 // spawn a dlight if requested
1168                                 if (info->lightradiusstart > 0 && spawndlight)
1169                                 {
1170                                         matrix4x4_t tempmatrix;
1171                                         if (info->trailspacing > 0)
1172                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1173                                         else
1174                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1175                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1176                                         {
1177                                                 // light flash (explosion, etc)
1178                                                 // called when effect starts
1179                                                 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);
1180                                         }
1181                                         else
1182                                         {
1183                                                 // glowing entity
1184                                                 // called by CL_LinkNetworkEntity
1185                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1186                                                 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);
1187                                         }
1188                                 }
1189
1190                                 if (!spawnparticles)
1191                                         continue;
1192
1193                                 // spawn particles
1194                                 tex = info->tex[0];
1195                                 if (info->tex[1] > info->tex[0])
1196                                 {
1197                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1198                                         tex = min(tex, info->tex[1] - 1);
1199                                 }
1200                                 if (info->particletype == pt_decal)
1201                                         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]);
1202                                 else if (info->particletype == pt_beam)
1203                                         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, lhrandom(info->time[0], info->time[1]));
1204                                 else
1205                                 {
1206                                         if (!cl_particles.integer)
1207                                                 continue;
1208                                         switch (info->particletype)
1209                                         {
1210                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1211                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1212                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1213                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1214                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1215                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1216                                         default: break;
1217                                         }
1218                                         VectorCopy(originmins, trailpos);
1219                                         if (info->trailspacing > 0)
1220                                         {
1221                                                 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1222                                                 trailstep = info->trailspacing / cl_particles_quality.value;
1223                                         }
1224                                         else
1225                                         {
1226                                                 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1227                                                 trailstep = 0;
1228                                         }
1229                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1230                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1231                                         {
1232                                                 if (info->tex[1] > info->tex[0])
1233                                                 {
1234                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1235                                                         tex = min(tex, info->tex[1] - 1);
1236                                                 }
1237                                                 if (!trailstep)
1238                                                 {
1239                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1240                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1241                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1242                                                 }
1243                                                 VectorRandom(rvec);
1244                                                 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, lhrandom(info->time[0], info->time[1]));
1245                                                 if (trailstep)
1246                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1247                                         }
1248                                 }
1249                         }
1250                 }
1251         }
1252         if (!found)
1253                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1254 }
1255
1256 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)
1257 {
1258         CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1259 }
1260
1261 /*
1262 ===============
1263 CL_EntityParticles
1264 ===============
1265 */
1266 void CL_EntityParticles (const entity_t *ent)
1267 {
1268         int i;
1269         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1270         static vec3_t avelocities[NUMVERTEXNORMALS];
1271         if (!cl_particles.integer) return;
1272         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1273
1274         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1275
1276         if (!avelocities[0][0])
1277                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1278                         avelocities[0][i] = lhrandom(0, 2.55);
1279
1280         for (i = 0;i < NUMVERTEXNORMALS;i++)
1281         {
1282                 yaw = cl.time * avelocities[i][0];
1283                 pitch = cl.time * avelocities[i][1];
1284                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1285                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1286                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1287                 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);
1288         }
1289 }
1290
1291
1292 void CL_ReadPointFile_f (void)
1293 {
1294         vec3_t org, leakorg;
1295         int r, c, s;
1296         char *pointfile = NULL, *pointfilepos, *t, tchar;
1297         char name[MAX_OSPATH];
1298
1299         if (!cl.worldmodel)
1300                 return;
1301
1302         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1303         strlcat (name, ".pts", sizeof (name));
1304         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1305         if (!pointfile)
1306         {
1307                 Con_Printf("Could not open %s\n", name);
1308                 return;
1309         }
1310
1311         Con_Printf("Reading %s...\n", name);
1312         VectorClear(leakorg);
1313         c = 0;
1314         s = 0;
1315         pointfilepos = pointfile;
1316         while (*pointfilepos)
1317         {
1318                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1319                         pointfilepos++;
1320                 if (!*pointfilepos)
1321                         break;
1322                 t = pointfilepos;
1323                 while (*t && *t != '\n' && *t != '\r')
1324                         t++;
1325                 tchar = *t;
1326                 *t = 0;
1327 #if _MSC_VER >= 1400
1328 #define sscanf sscanf_s
1329 #endif
1330                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1331                 *t = tchar;
1332                 pointfilepos = t;
1333                 if (r != 3)
1334                         break;
1335                 if (c == 0)
1336                         VectorCopy(org, leakorg);
1337                 c++;
1338
1339                 if (cl.num_particles < cl.max_particles - 3)
1340                 {
1341                         s++;
1342                         CL_NewParticle(pt_alphastatic, 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);
1343                 }
1344         }
1345         Mem_Free(pointfile);
1346         VectorCopy(leakorg, org);
1347         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1348
1349         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);
1350         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);
1351         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);
1352 }
1353
1354 /*
1355 ===============
1356 CL_ParseParticleEffect
1357
1358 Parse an effect out of the server message
1359 ===============
1360 */
1361 void CL_ParseParticleEffect (void)
1362 {
1363         vec3_t org, dir;
1364         int i, count, msgcount, color;
1365
1366         MSG_ReadVector(org, cls.protocol);
1367         for (i=0 ; i<3 ; i++)
1368                 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1369         msgcount = MSG_ReadByte ();
1370         color = MSG_ReadByte ();
1371
1372         if (msgcount == 255)
1373                 count = 1024;
1374         else
1375                 count = msgcount;
1376
1377         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1378 }
1379
1380 /*
1381 ===============
1382 CL_ParticleExplosion
1383
1384 ===============
1385 */
1386 void CL_ParticleExplosion (const vec3_t org)
1387 {
1388         int i;
1389         trace_t trace;
1390         //vec3_t v;
1391         //vec3_t v2;
1392         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1393         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1394
1395         if (cl_particles_quake.integer)
1396         {
1397                 for (i = 0;i < 1024;i++)
1398                 {
1399                         int r, color;
1400                         r = rand()&3;
1401                         if (i & 1)
1402                         {
1403                                 color = particlepalette[ramp1[r]];
1404                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r));
1405                         }
1406                         else
1407                         {
1408                                 color = particlepalette[ramp2[r]];
1409                                 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r));
1410                         }
1411                 }
1412         }
1413         else
1414         {
1415                 i = CL_PointSuperContents(org);
1416                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1417                 {
1418                         if (cl_particles.integer && cl_particles_bubbles.integer)
1419                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1420                                         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);
1421                 }
1422                 else
1423                 {
1424                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1425                         {
1426                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1427                                 {
1428                                         int k;
1429                                         vec3_t v, v2;
1430                                         for (k = 0;k < 16;k++)
1431                                         {
1432                                                 VectorRandom(v2);
1433                                                 VectorMA(org, 128, v2, v);
1434                                                 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1435                                                 if (trace.fraction >= 0.1)
1436                                                         break;
1437                                         }
1438                                         VectorSubtract(trace.endpos, org, v2);
1439                                         VectorScale(v2, 2.0f, v2);
1440                                         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);
1441                                 }
1442                         }
1443                 }
1444         }
1445
1446         if (cl_particles_explosions_shell.integer)
1447                 R_NewExplosion(org);
1448 }
1449
1450 /*
1451 ===============
1452 CL_ParticleExplosion2
1453
1454 ===============
1455 */
1456 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1457 {
1458         int i, k;
1459         if (!cl_particles.integer) return;
1460
1461         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1462         {
1463                 k = particlepalette[colorStart + (i % colorLength)];
1464                 if (cl_particles_quake.integer)
1465                         CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3);
1466                 else
1467                         CL_NewParticle(pt_alphastatic, 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);
1468         }
1469 }
1470
1471 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1472 {
1473         if (cl_particles_sparks.integer)
1474         {
1475                 sparkcount *= cl_particles_quality.value;
1476                 while(sparkcount-- > 0)
1477                         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);
1478         }
1479 }
1480
1481 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1482 {
1483         if (cl_particles_smoke.integer)
1484         {
1485                 smokecount *= cl_particles_quality.value;
1486                 while(smokecount-- > 0)
1487                         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);
1488         }
1489 }
1490
1491 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)
1492 {
1493         int k;
1494         if (!cl_particles.integer) return;
1495
1496         count = (int)(count * cl_particles_quality.value);
1497         while (count--)
1498         {
1499                 k = particlepalette[colorbase + (rand()&3)];
1500                 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);
1501         }
1502 }
1503
1504 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1505 {
1506         int k;
1507         float minz, maxz, lifetime = 30;
1508         if (!cl_particles.integer) return;
1509         if (dir[2] < 0) // falling
1510         {
1511                 minz = maxs[2] + dir[2] * 0.1;
1512                 maxz = maxs[2];
1513                 if (cl.worldmodel)
1514                         lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1515         }
1516         else // rising??
1517         {
1518                 minz = mins[2];
1519                 maxz = maxs[2] + dir[2] * 0.1;
1520                 if (cl.worldmodel)
1521                         lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1522         }
1523
1524         count = (int)(count * cl_particles_quality.value);
1525
1526         switch(type)
1527         {
1528         case 0:
1529                 if (!cl_particles_rain.integer) break;
1530                 count *= 4; // ick, this should be in the mod or maps?
1531
1532                 while(count--)
1533                 {
1534                         k = particlepalette[colorbase + (rand()&3)];
1535                         if (gamemode == GAME_GOODVSBAD2)
1536                                 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);
1537                         else
1538                                 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);
1539                 }
1540                 break;
1541         case 1:
1542                 if (!cl_particles_snow.integer) break;
1543                 while(count--)
1544                 {
1545                         k = particlepalette[colorbase + (rand()&3)];
1546                         if (gamemode == GAME_GOODVSBAD2)
1547                                 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);
1548                         else
1549                                 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);
1550                 }
1551                 break;
1552         default:
1553                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1554         }
1555 }
1556
1557 #define MAX_PARTICLETEXTURES 64
1558 // particletexture_t is a rectangle in the particlefonttexture
1559 typedef struct particletexture_s
1560 {
1561         rtexture_t *texture;
1562         float s1, t1, s2, t2;
1563 }
1564 particletexture_t;
1565
1566 static rtexturepool_t *particletexturepool;
1567 static rtexture_t *particlefonttexture;
1568 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1569
1570 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1571 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1572 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1573 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1574
1575 #define PARTICLETEXTURESIZE 64
1576 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1577
1578 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1579 {
1580         float dz, f, dot;
1581         vec3_t normal;
1582         dz = 1 - (dx*dx+dy*dy);
1583         if (dz > 0) // it does hit the sphere
1584         {
1585                 f = 0;
1586                 // back side
1587                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1588                 VectorNormalize(normal);
1589                 dot = DotProduct(normal, light);
1590                 if (dot > 0.5) // interior reflection
1591                         f += ((dot *  2) - 1);
1592                 else if (dot < -0.5) // exterior reflection
1593                         f += ((dot * -2) - 1);
1594                 // front side
1595                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1596                 VectorNormalize(normal);
1597                 dot = DotProduct(normal, light);
1598                 if (dot > 0.5) // interior reflection
1599                         f += ((dot *  2) - 1);
1600                 else if (dot < -0.5) // exterior reflection
1601                         f += ((dot * -2) - 1);
1602                 f *= 128;
1603                 f += 16; // just to give it a haze so you can see the outline
1604                 f = bound(0, f, 255);
1605                 return (unsigned char) f;
1606         }
1607         else
1608                 return 0;
1609 }
1610
1611 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1612 {
1613         int basex, basey, y;
1614         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1615         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1616         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1617                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1618 }
1619
1620 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1621 {
1622         int x, y;
1623         float cx, cy, dx, dy, f, iradius;
1624         unsigned char *d;
1625         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1626         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1627         iradius = 1.0f / radius;
1628         alpha *= (1.0f / 255.0f);
1629         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1630         {
1631                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1632                 {
1633                         dx = (x - cx);
1634                         dy = (y - cy);
1635                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1636                         if (f > 0)
1637                         {
1638                                 if (f > 1)
1639                                         f = 1;
1640                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1641                                 d[0] += (int)(f * (blue  - d[0]));
1642                                 d[1] += (int)(f * (green - d[1]));
1643                                 d[2] += (int)(f * (red   - d[2]));
1644                         }
1645                 }
1646         }
1647 }
1648
1649 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1650 {
1651         int i;
1652         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1653         {
1654                 data[0] = bound(minb, data[0], maxb);
1655                 data[1] = bound(ming, data[1], maxg);
1656                 data[2] = bound(minr, data[2], maxr);
1657         }
1658 }
1659
1660 void particletextureinvert(unsigned char *data)
1661 {
1662         int i;
1663         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1664         {
1665                 data[0] = 255 - data[0];
1666                 data[1] = 255 - data[1];
1667                 data[2] = 255 - data[2];
1668         }
1669 }
1670
1671 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1672 static void R_InitBloodTextures (unsigned char *particletexturedata)
1673 {
1674         int i, j, k, m;
1675         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1676
1677         // blood particles
1678         for (i = 0;i < 8;i++)
1679         {
1680                 memset(&data[0][0][0], 255, sizeof(data));
1681                 for (k = 0;k < 24;k++)
1682                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1683                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1684                 particletextureinvert(&data[0][0][0]);
1685                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1686         }
1687
1688         // blood decals
1689         for (i = 0;i < 8;i++)
1690         {
1691                 memset(&data[0][0][0], 255, sizeof(data));
1692                 m = 8;
1693                 for (j = 1;j < 10;j++)
1694                         for (k = min(j, m - 1);k < m;k++)
1695                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1696                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1697                 particletextureinvert(&data[0][0][0]);
1698                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1699         }
1700
1701 }
1702
1703 //uncomment this to make engine save out particle font to a tga file when run
1704 //#define DUMPPARTICLEFONT
1705
1706 static void R_InitParticleTexture (void)
1707 {
1708         int x, y, d, i, k, m;
1709         float dx, dy, f;
1710         vec3_t light;
1711
1712         // a note: decals need to modulate (multiply) the background color to
1713         // properly darken it (stain), and they need to be able to alpha fade,
1714         // this is a very difficult challenge because it means fading to white
1715         // (no change to background) rather than black (darkening everything
1716         // behind the whole decal polygon), and to accomplish this the texture is
1717         // inverted (dark red blood on white background becomes brilliant cyan
1718         // and white on black background) so we can alpha fade it to black, then
1719         // we invert it again during the blendfunc to make it work...
1720
1721 #ifndef DUMPPARTICLEFONT
1722         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1723         if (!particlefonttexture)
1724 #endif
1725         {
1726                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1727                 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1728                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1729
1730                 // smoke
1731                 for (i = 0;i < 8;i++)
1732                 {
1733                         memset(&data[0][0][0], 255, sizeof(data));
1734                         do
1735                         {
1736                                 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1737
1738                                 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1739                                 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1740                                 m = 0;
1741                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1742                                 {
1743                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1744                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1745                                         {
1746                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1747                                                 d = (noise2[y][x] - 128) * 3 + 192;
1748                                                 if (d > 0)
1749                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
1750                                                 d = (d * noise1[y][x]) >> 7;
1751                                                 d = bound(0, d, 255);
1752                                                 data[y][x][3] = (unsigned char) d;
1753                                                 if (m < d)
1754                                                         m = d;
1755                                         }
1756                                 }
1757                         }
1758                         while (m < 224);
1759                         setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1760                 }
1761
1762                 // rain splash
1763                 memset(&data[0][0][0], 255, sizeof(data));
1764                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1765                 {
1766                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1767                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1768                         {
1769                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1770                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1771                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1772                         }
1773                 }
1774                 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1775
1776                 // normal particle
1777                 memset(&data[0][0][0], 255, sizeof(data));
1778                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1779                 {
1780                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1781                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1782                         {
1783                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1784                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1785                                 d = bound(0, d, 255);
1786                                 data[y][x][3] = (unsigned char) d;
1787                         }
1788                 }
1789                 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1790
1791                 // rain
1792                 memset(&data[0][0][0], 255, sizeof(data));
1793                 light[0] = 1;light[1] = 1;light[2] = 1;
1794                 VectorNormalize(light);
1795                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1796                 {
1797                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1798                         // stretch upper half of bubble by +50% and shrink lower half by -50%
1799                         // (this gives an elongated teardrop shape)
1800                         if (dy > 0.5f)
1801                                 dy = (dy - 0.5f) * 2.0f;
1802                         else
1803                                 dy = (dy - 0.5f) / 1.5f;
1804                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1805                         {
1806                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1807                                 // shrink bubble width to half
1808                                 dx *= 2.0f;
1809                                 data[y][x][3] = shadebubble(dx, dy, light);
1810                         }
1811                 }
1812                 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1813
1814                 // bubble
1815                 memset(&data[0][0][0], 255, sizeof(data));
1816                 light[0] = 1;light[1] = 1;light[2] = 1;
1817                 VectorNormalize(light);
1818                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1819                 {
1820                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1821                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1822                         {
1823                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1824                                 data[y][x][3] = shadebubble(dx, dy, light);
1825                         }
1826                 }
1827                 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1828
1829                 // Blood particles and blood decals
1830                 R_InitBloodTextures (particletexturedata);
1831
1832                 // bullet decals
1833                 for (i = 0;i < 8;i++)
1834                 {
1835                         memset(&data[0][0][0], 255, sizeof(data));
1836                         for (k = 0;k < 12;k++)
1837                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1838                         for (k = 0;k < 3;k++)
1839                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1840                         //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1841                         particletextureinvert(&data[0][0][0]);
1842                         setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1843                 }
1844
1845 #ifdef DUMPPARTICLEFONT
1846                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1847 #endif
1848
1849                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1850
1851                 Mem_Free(particletexturedata);
1852         }
1853         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1854         {
1855                 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1856                 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1857                 particletexture[i].texture = particlefonttexture;
1858                 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1859                 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1860                 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1861                 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1862         }
1863
1864 #ifndef DUMPPARTICLEFONT
1865         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1866         if (!particletexture[tex_beam].texture)
1867 #endif
1868         {
1869                 unsigned char noise3[64][64], data2[64][16][4];
1870                 // nexbeam
1871                 fractalnoise(&noise3[0][0], 64, 4);
1872                 m = 0;
1873                 for (y = 0;y < 64;y++)
1874                 {
1875                         dy = (y - 0.5f*64) / (64*0.5f-1);
1876                         for (x = 0;x < 16;x++)
1877                         {
1878                                 dx = (x - 0.5f*16) / (16*0.5f-2);
1879                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1880                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1881                                 data2[y][x][3] = 255;
1882                         }
1883                 }
1884
1885 #ifdef DUMPPARTICLEFONT
1886                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1887 #endif
1888                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1889         }
1890         particletexture[tex_beam].s1 = 0;
1891         particletexture[tex_beam].t1 = 0;
1892         particletexture[tex_beam].s2 = 1;
1893         particletexture[tex_beam].t2 = 1;
1894 }
1895
1896 static void r_part_start(void)
1897 {
1898         int i;
1899         // generate particlepalette for convenience from the main one
1900         for (i = 0;i < 256;i++)
1901                 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1902         particletexturepool = R_AllocTexturePool();
1903         R_InitParticleTexture ();
1904         CL_Particles_LoadEffectInfo();
1905 }
1906
1907 static void r_part_shutdown(void)
1908 {
1909         R_FreeTexturePool(&particletexturepool);
1910 }
1911
1912 static void r_part_newmap(void)
1913 {
1914         CL_Particles_LoadEffectInfo();
1915 }
1916
1917 #define BATCHSIZE 256
1918 unsigned short particle_elements[BATCHSIZE*6];
1919
1920 void R_Particles_Init (void)
1921 {
1922         int i;
1923         for (i = 0;i < BATCHSIZE;i++)
1924         {
1925                 particle_elements[i*6+0] = i*4+0;
1926                 particle_elements[i*6+1] = i*4+1;
1927                 particle_elements[i*6+2] = i*4+2;
1928                 particle_elements[i*6+3] = i*4+0;
1929                 particle_elements[i*6+4] = i*4+2;
1930                 particle_elements[i*6+5] = i*4+3;
1931         }
1932
1933         Cvar_RegisterVariable(&r_drawparticles);
1934         Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1935         Cvar_RegisterVariable(&r_drawdecals);
1936         Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1937         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1938 }
1939
1940 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1941 {
1942         int surfacelistindex;
1943         const decal_t *d;
1944         float *v3f, *t2f, *c4f;
1945         particletexture_t *tex;
1946         float right[3], up[3], size, ca;
1947         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1948         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1949
1950         r_refdef.stats.decals += numsurfaces;
1951         R_Mesh_Matrix(&identitymatrix);
1952         R_Mesh_ResetTextureState();
1953         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1954         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1955         R_Mesh_ColorPointer(particle_color4f, 0, 0);
1956         R_SetupGenericShader(true);
1957         GL_DepthMask(false);
1958         GL_DepthRange(0, 1);
1959         GL_PolygonOffset(0, 0);
1960         GL_DepthTest(true);
1961         GL_CullFace(GL_NONE);
1962
1963         // generate all the vertices at once
1964         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1965         {
1966                 d = cl.decals + surfacelist[surfacelistindex];
1967
1968                 // calculate color
1969                 c4f = particle_color4f + 16*surfacelistindex;
1970                 ca = d->alpha * alphascale;
1971                 if (r_refdef.fogenabled)
1972                         ca *= FogPoint_World(d->org);
1973                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1974                 Vector4Copy(c4f, c4f + 4);
1975                 Vector4Copy(c4f, c4f + 8);
1976                 Vector4Copy(c4f, c4f + 12);
1977
1978                 // calculate vertex positions
1979                 size = d->size * cl_particles_size.value;
1980                 VectorVectors(d->normal, right, up);
1981                 VectorScale(right, size, right);
1982                 VectorScale(up, size, up);
1983                 v3f = particle_vertex3f + 12*surfacelistindex;
1984                 v3f[ 0] = d->org[0] - right[0] - up[0];
1985                 v3f[ 1] = d->org[1] - right[1] - up[1];
1986                 v3f[ 2] = d->org[2] - right[2] - up[2];
1987                 v3f[ 3] = d->org[0] - right[0] + up[0];
1988                 v3f[ 4] = d->org[1] - right[1] + up[1];
1989                 v3f[ 5] = d->org[2] - right[2] + up[2];
1990                 v3f[ 6] = d->org[0] + right[0] + up[0];
1991                 v3f[ 7] = d->org[1] + right[1] + up[1];
1992                 v3f[ 8] = d->org[2] + right[2] + up[2];
1993                 v3f[ 9] = d->org[0] + right[0] - up[0];
1994                 v3f[10] = d->org[1] + right[1] - up[1];
1995                 v3f[11] = d->org[2] + right[2] - up[2];
1996
1997                 // calculate texcoords
1998                 tex = &particletexture[d->texnum];
1999                 t2f = particle_texcoord2f + 8*surfacelistindex;
2000                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2001                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2002                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2003                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2004         }
2005
2006         // now render the decals all at once
2007         // (this assumes they all use one particle font texture!)
2008         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2009         R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2010         GL_LockArrays(0, numsurfaces*4);
2011         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2012         GL_LockArrays(0, 0);
2013 }
2014
2015 void R_DrawDecals (void)
2016 {
2017         int i;
2018         decal_t *decal;
2019         float frametime;
2020         float decalfade;
2021         float drawdist2;
2022
2023         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2024         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2025
2026         // LordHavoc: early out conditions
2027         if ((!cl.num_decals) || (!r_drawdecals.integer))
2028                 return;
2029
2030         decalfade = frametime * 256 / cl_decals_fadetime.value;
2031         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2032         drawdist2 = drawdist2*drawdist2;
2033
2034         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2035         {
2036                 if (!decal->typeindex)
2037                         continue;
2038
2039                 if (cl.time > decal->time2 + cl_decals_time.value)
2040                 {
2041                         decal->alpha -= decalfade;
2042                         if (decal->alpha <= 0)
2043                                 goto killdecal;
2044                 }
2045
2046                 if (decal->owner)
2047                 {
2048                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2049                         {
2050                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2051                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2052                         }
2053                         else
2054                                 goto killdecal;
2055                 }
2056
2057                 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))
2058                         R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2059                 continue;
2060 killdecal:
2061                 decal->typeindex = 0;
2062                 if (cl.free_decal > i)
2063                         cl.free_decal = i;
2064         }
2065
2066         // reduce cl.num_decals if possible
2067         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2068                 cl.num_decals--;
2069
2070         if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2071         {
2072                 decal_t *olddecals = cl.decals;
2073                 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2074                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2075                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2076                 Mem_Free(olddecals);
2077         }
2078 }
2079
2080 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2081 {
2082         int surfacelistindex;
2083         int batchstart, batchcount;
2084         const particle_t *p;
2085         pblend_t blendmode;
2086         rtexture_t *texture;
2087         float *v3f, *t2f, *c4f;
2088         particletexture_t *tex;
2089         float up2[3], v[3], right[3], up[3], fog, ifog, size;
2090         float ambient[3], diffuse[3], diffusenormal[3];
2091         vec4_t colormultiplier;
2092         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2093
2094         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));
2095
2096         r_refdef.stats.particles += numsurfaces;
2097         R_Mesh_Matrix(&identitymatrix);
2098         R_Mesh_ResetTextureState();
2099         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2100         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2101         R_Mesh_ColorPointer(particle_color4f, 0, 0);
2102         R_SetupGenericShader(true);
2103         GL_DepthMask(false);
2104         GL_DepthRange(0, 1);
2105         GL_PolygonOffset(0, 0);
2106         GL_DepthTest(true);
2107         GL_CullFace(GL_NONE);
2108
2109         // first generate all the vertices at once
2110         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2111         {
2112                 p = cl.particles + surfacelist[surfacelistindex];
2113
2114                 blendmode = particletype[p->typeindex].blendmode;
2115
2116                 c4f[0] = p->color[0] * colormultiplier[0];
2117                 c4f[1] = p->color[1] * colormultiplier[1];
2118                 c4f[2] = p->color[2] * colormultiplier[2];
2119                 c4f[3] = p->alpha * colormultiplier[3];
2120                 switch (blendmode)
2121                 {
2122                 case PBLEND_MOD:
2123                 case PBLEND_ADD:
2124                         // additive and modulate can just fade out in fog (this is correct)
2125                         if (r_refdef.fogenabled)
2126                                 c4f[3] *= FogPoint_World(p->org);
2127                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2128                         c4f[0] *= c4f[3];
2129                         c4f[1] *= c4f[3];
2130                         c4f[2] *= c4f[3];
2131                         c4f[3] = 1;
2132                         break;
2133                 case PBLEND_ALPHA:
2134                         // note: lighting is not cheap!
2135                         if (particletype[p->typeindex].lighting)
2136                         {
2137                                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2138                                 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2139                                 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2140                                 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2141                         }
2142                         // mix in the fog color
2143                         if (r_refdef.fogenabled)
2144                         {
2145                                 fog = FogPoint_World(p->org);
2146                                 ifog = 1 - fog;
2147                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2148                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2149                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2150                         }
2151                         break;
2152                 }
2153                 // copy the color into the other three vertices
2154                 Vector4Copy(c4f, c4f + 4);
2155                 Vector4Copy(c4f, c4f + 8);
2156                 Vector4Copy(c4f, c4f + 12);
2157
2158                 size = p->size * cl_particles_size.value;
2159                 tex = &particletexture[p->texnum];
2160                 switch(particletype[p->typeindex].orientation)
2161                 {
2162                 case PARTICLE_BILLBOARD:
2163                         VectorScale(r_refdef.view.left, -size, right);
2164                         VectorScale(r_refdef.view.up, size, up);
2165                         v3f[ 0] = p->org[0] - right[0] - up[0];
2166                         v3f[ 1] = p->org[1] - right[1] - up[1];
2167                         v3f[ 2] = p->org[2] - right[2] - up[2];
2168                         v3f[ 3] = p->org[0] - right[0] + up[0];
2169                         v3f[ 4] = p->org[1] - right[1] + up[1];
2170                         v3f[ 5] = p->org[2] - right[2] + up[2];
2171                         v3f[ 6] = p->org[0] + right[0] + up[0];
2172                         v3f[ 7] = p->org[1] + right[1] + up[1];
2173                         v3f[ 8] = p->org[2] + right[2] + up[2];
2174                         v3f[ 9] = p->org[0] + right[0] - up[0];
2175                         v3f[10] = p->org[1] + right[1] - up[1];
2176                         v3f[11] = p->org[2] + right[2] - up[2];
2177                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2178                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2179                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2180                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2181                         break;
2182                 case PARTICLE_ORIENTED_DOUBLESIDED:
2183                         VectorVectors(p->vel, right, up);
2184                         VectorScale(right, size, right);
2185                         VectorScale(up, size, up);
2186                         v3f[ 0] = p->org[0] - right[0] - up[0];
2187                         v3f[ 1] = p->org[1] - right[1] - up[1];
2188                         v3f[ 2] = p->org[2] - right[2] - up[2];
2189                         v3f[ 3] = p->org[0] - right[0] + up[0];
2190                         v3f[ 4] = p->org[1] - right[1] + up[1];
2191                         v3f[ 5] = p->org[2] - right[2] + up[2];
2192                         v3f[ 6] = p->org[0] + right[0] + up[0];
2193                         v3f[ 7] = p->org[1] + right[1] + up[1];
2194                         v3f[ 8] = p->org[2] + right[2] + up[2];
2195                         v3f[ 9] = p->org[0] + right[0] - up[0];
2196                         v3f[10] = p->org[1] + right[1] - up[1];
2197                         v3f[11] = p->org[2] + right[2] - up[2];
2198                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2199                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2200                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2201                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2202                         break;
2203                 case PARTICLE_SPARK:
2204                         VectorMA(p->org, -0.04, p->vel, v);
2205                         VectorMA(p->org, 0.04, p->vel, up2);
2206                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2207                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2208                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2209                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2210                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2211                         break;
2212                 case PARTICLE_BEAM:
2213                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2214                         VectorSubtract(p->vel, p->org, up);
2215                         VectorNormalize(up);
2216                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2217                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2218                         t2f[0] = 1;t2f[1] = v[0];
2219                         t2f[2] = 0;t2f[3] = v[0];
2220                         t2f[4] = 0;t2f[5] = v[1];
2221                         t2f[6] = 1;t2f[7] = v[1];
2222                         break;
2223                 }
2224         }
2225
2226         // now render batches of particles based on blendmode and texture
2227         blendmode = -1;
2228         texture = NULL;
2229         GL_LockArrays(0, numsurfaces*4);
2230         batchstart = 0;
2231         batchcount = 0;
2232         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2233         {
2234                 p = cl.particles + surfacelist[surfacelistindex];
2235
2236                 if (blendmode != particletype[p->typeindex].blendmode)
2237                 {
2238                         blendmode = particletype[p->typeindex].blendmode;
2239                         switch(blendmode)
2240                         {
2241                         case PBLEND_ALPHA:
2242                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2243                                 break;
2244                         case PBLEND_ADD:
2245                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2246                                 break;
2247                         case PBLEND_MOD:
2248                                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2249                                 break;
2250                         }
2251                 }
2252                 if (texture != particletexture[p->texnum].texture)
2253                 {
2254                         texture = particletexture[p->texnum].texture;
2255                         R_Mesh_TexBind(0, R_GetTexture(texture));
2256                 }
2257
2258                 // iterate until we find a change in settings
2259                 batchstart = surfacelistindex++;
2260                 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2261                 {
2262                         p = cl.particles + surfacelist[surfacelistindex];
2263                         if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2264                                 break;
2265                 }
2266
2267                 batchcount = surfacelistindex - batchstart;
2268                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2269         }
2270         GL_LockArrays(0, 0);
2271 }
2272
2273 void R_DrawParticles (void)
2274 {
2275         int i, a, content;
2276         float minparticledist;
2277         particle_t *p;
2278         float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2279         float drawdist2;
2280         int hitent;
2281         trace_t trace;
2282         qboolean update;
2283
2284         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2285         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2286
2287         // LordHavoc: early out conditions
2288         if ((!cl.num_particles) || (!r_drawparticles.integer))
2289                 return;
2290
2291         minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2292         gravity = frametime * cl.movevars_gravity;
2293         dvel = 1+4*frametime;
2294         decalfade = frametime * 255 / cl_decals_fadetime.value;
2295         update = frametime > 0;
2296         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2297         drawdist2 = drawdist2*drawdist2;
2298
2299         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2300         {
2301                 if (!p->typeindex)
2302                 {
2303                         if (cl.free_particle > i)
2304                                 cl.free_particle = i;
2305                         continue;
2306                 }
2307
2308                 if (update)
2309                 {
2310                         if (p->delayedspawn > cl.time)
2311                                 continue;
2312                         p->delayedspawn = 0;
2313
2314                         content = 0;
2315
2316                         p->size += p->sizeincrease * frametime;
2317                         p->alpha -= p->alphafade * frametime;
2318
2319                         if (p->alpha <= 0 || p->die <= cl.time)
2320                                 goto killparticle;
2321
2322                         if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2323                         {
2324                                 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2325                                 {
2326                                         if (p->typeindex == pt_blood)
2327                                                 p->size += frametime * 8;
2328                                         else
2329                                                 p->vel[2] -= p->gravity * gravity;
2330                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2331                                         VectorScale(p->vel, f, p->vel);
2332                                 }
2333                                 else
2334                                 {
2335                                         p->vel[2] -= p->gravity * gravity;
2336                                         if (p->airfriction)
2337                                         {
2338                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2339                                                 VectorScale(p->vel, f, p->vel);
2340                                         }
2341                                 }
2342
2343                                 VectorCopy(p->org, oldorg);
2344                                 VectorMA(p->org, frametime, p->vel, p->org);
2345                                 if (p->bounce && cl.time >= p->delayedcollisions)
2346                                 {
2347                                         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);
2348                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2349                                         // or if the trace hit something flagged as NOIMPACT
2350                                         // then remove the particle
2351                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2352                                                 goto killparticle;
2353                                         VectorCopy(trace.endpos, p->org);
2354                                         // react if the particle hit something
2355                                         if (trace.fraction < 1)
2356                                         {
2357                                                 VectorCopy(trace.endpos, p->org);
2358                                                 if (p->typeindex == pt_blood)
2359                                                 {
2360                                                         // blood - splash on solid
2361                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2362                                                                 goto killparticle;
2363                                                         R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2364                                                         if (cl_decals.integer)
2365                                                         {
2366                                                                 // create a decal for the blood splat
2367                                                                 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);
2368                                                         }
2369                                                         goto killparticle;
2370                                                 }
2371                                                 else if (p->bounce < 0)
2372                                                 {
2373                                                         // bounce -1 means remove on impact
2374                                                         goto killparticle;
2375                                                 }
2376                                                 else
2377                                                 {
2378                                                         // anything else - bounce off solid
2379                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2380                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2381                                                         if (DotProduct(p->vel, p->vel) < 0.03)
2382                                                                 VectorClear(p->vel);
2383                                                 }
2384                                         }
2385                                 }
2386                         }
2387
2388                         if (p->typeindex != pt_static)
2389                         {
2390                                 switch (p->typeindex)
2391                                 {
2392                                 case pt_entityparticle:
2393                                         // particle that removes itself after one rendered frame
2394                                         if (p->time2)
2395                                                 goto killparticle;
2396                                         else
2397                                                 p->time2 = 1;
2398                                         break;
2399                                 case pt_blood:
2400                                         a = CL_PointSuperContents(p->org);
2401                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2402                                                 goto killparticle;
2403                                         break;
2404                                 case pt_bubble:
2405                                         a = CL_PointSuperContents(p->org);
2406                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2407                                                 goto killparticle;
2408                                         break;
2409                                 case pt_rain:
2410                                         a = CL_PointSuperContents(p->org);
2411                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2412                                                 goto killparticle;
2413                                         break;
2414                                 case pt_snow:
2415                                         if (cl.time > p->time2)
2416                                         {
2417                                                 // snow flutter
2418                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
2419                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2420                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2421                                         }
2422                                         a = CL_PointSuperContents(p->org);
2423                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2424                                                 goto killparticle;
2425                                         break;
2426                                 default:
2427                                         break;
2428                                 }
2429                         }
2430                 }
2431                 else if (p->delayedspawn)
2432                         continue;
2433
2434                 // don't render particles too close to the view (they chew fillrate)
2435                 // also don't render particles behind the view (useless)
2436                 // further checks to cull to the frustum would be too slow here
2437                 switch(p->typeindex)
2438                 {
2439                 case pt_beam:
2440                         // beams have no culling
2441                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2442                         break;
2443                 default:
2444                         // anything else just has to be in front of the viewer and visible at this distance
2445                         if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2446                                 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2447                         break;
2448                 }
2449
2450                 continue;
2451 killparticle:
2452                 p->typeindex = 0;
2453                 if (cl.free_particle > i)
2454                         cl.free_particle = i;
2455         }
2456
2457         // reduce cl.num_particles if possible
2458         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2459                 cl.num_particles--;
2460
2461         if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2462         {
2463                 particle_t *oldparticles = cl.particles;
2464                 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2465                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2466                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2467                 Mem_Free(oldparticles);
2468         }
2469 }