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