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