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