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