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