]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
fix r_shadows_focus cvar so it is properly relative to view directions
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #include "cl_collision.h"
24 #include "image.h"
25 #include "r_shadow.h"
26
27 // 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                 // ensure alpha multiplier saturates properly
2260                 if (ca > 1.0f / 256.0f)
2261                         ca = 1.0f / 256.0f;     
2262                 if (r_refdef.fogenabled)
2263                         ca *= RSurf_FogVertex(d->org);
2264                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2265                 Vector4Copy(c4f, c4f + 4);
2266                 Vector4Copy(c4f, c4f + 8);
2267                 Vector4Copy(c4f, c4f + 12);
2268
2269                 // calculate vertex positions
2270                 size = d->size * cl_particles_size.value;
2271                 VectorVectors(d->normal, right, up);
2272                 VectorScale(right, size, right);
2273                 VectorScale(up, size, up);
2274                 v3f = particle_vertex3f + 12*surfacelistindex;
2275                 v3f[ 0] = d->org[0] - right[0] - up[0];
2276                 v3f[ 1] = d->org[1] - right[1] - up[1];
2277                 v3f[ 2] = d->org[2] - right[2] - up[2];
2278                 v3f[ 3] = d->org[0] - right[0] + up[0];
2279                 v3f[ 4] = d->org[1] - right[1] + up[1];
2280                 v3f[ 5] = d->org[2] - right[2] + up[2];
2281                 v3f[ 6] = d->org[0] + right[0] + up[0];
2282                 v3f[ 7] = d->org[1] + right[1] + up[1];
2283                 v3f[ 8] = d->org[2] + right[2] + up[2];
2284                 v3f[ 9] = d->org[0] + right[0] - up[0];
2285                 v3f[10] = d->org[1] + right[1] - up[1];
2286                 v3f[11] = d->org[2] + right[2] - up[2];
2287
2288                 // calculate texcoords
2289                 tex = &particletexture[d->texnum];
2290                 t2f = particle_texcoord2f + 8*surfacelistindex;
2291                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2292                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2293                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2294                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2295         }
2296
2297         // now render the decals all at once
2298         // (this assumes they all use one particle font texture!)
2299         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2300         R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2301         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2302 }
2303
2304 void R_DrawDecals (void)
2305 {
2306         int i;
2307         int drawdecals = r_drawdecals.integer;
2308         decal_t *decal;
2309         float frametime;
2310         float decalfade;
2311         float drawdist2;
2312         int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2313
2314         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2315         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2316
2317         // LordHavoc: early out conditions
2318         if (!cl.num_decals)
2319                 return;
2320
2321         decalfade = frametime * 256 / cl_decals_fadetime.value;
2322         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2323         drawdist2 = drawdist2*drawdist2;
2324
2325         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2326         {
2327                 if (!decal->typeindex)
2328                         continue;
2329
2330                 if (killsequence - decal->decalsequence > 0)
2331                         goto killdecal;
2332
2333                 if (cl.time > decal->time2 + cl_decals_time.value)
2334                 {
2335                         decal->alpha -= decalfade;
2336                         if (decal->alpha <= 0)
2337                                 goto killdecal;
2338                 }
2339
2340                 if (decal->owner)
2341                 {
2342                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2343                         {
2344                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2345                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2346                         }
2347                         else
2348                                 goto killdecal;
2349                 }
2350
2351                 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2352                         continue;
2353
2354                 if (!drawdecals)
2355                         continue;
2356
2357                 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))
2358                         R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2359                 continue;
2360 killdecal:
2361                 decal->typeindex = 0;
2362                 if (cl.free_decal > i)
2363                         cl.free_decal = i;
2364         }
2365
2366         // reduce cl.num_decals if possible
2367         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2368                 cl.num_decals--;
2369
2370         if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2371         {
2372                 decal_t *olddecals = cl.decals;
2373                 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2374                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2375                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2376                 Mem_Free(olddecals);
2377         }
2378
2379         r_refdef.stats.totaldecals = cl.num_decals;
2380 }
2381
2382 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2383 {
2384         int surfacelistindex;
2385         int batchstart, batchcount;
2386         const particle_t *p;
2387         pblend_t blendmode;
2388         rtexture_t *texture;
2389         float *v3f, *t2f, *c4f;
2390         particletexture_t *tex;
2391         float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2392         float ambient[3], diffuse[3], diffusenormal[3];
2393         vec4_t colormultiplier;
2394
2395         RSurf_ActiveWorldEntity();
2396
2397         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));
2398
2399         r_refdef.stats.particles += numsurfaces;
2400         R_Mesh_ResetTextureState();
2401         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2402         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2403         R_Mesh_ColorPointer(particle_color4f, 0, 0);
2404         GL_DepthMask(false);
2405         GL_DepthRange(0, 1);
2406         GL_PolygonOffset(0, 0);
2407         GL_DepthTest(true);
2408         GL_CullFace(GL_NONE);
2409
2410         // first generate all the vertices at once
2411         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2412         {
2413                 p = cl.particles + surfacelist[surfacelistindex];
2414
2415                 blendmode = p->blendmode;
2416
2417                 switch (blendmode)
2418                 {
2419                 case PBLEND_INVALID:
2420                 case PBLEND_INVMOD:
2421                         alpha = p->alpha * colormultiplier[3];
2422                         // ensure alpha multiplier saturates properly
2423                         if (alpha > 1.0f)
2424                                 alpha = 1.0f;
2425                         // additive and modulate can just fade out in fog (this is correct)
2426                         if (r_refdef.fogenabled)
2427                                 alpha *= RSurf_FogVertex(p->org);
2428                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2429                         alpha *= 1.0f / 256.0f;
2430                         c4f[0] = p->color[0] * alpha;
2431                         c4f[1] = p->color[1] * alpha;
2432                         c4f[2] = p->color[2] * alpha;
2433                         c4f[3] = 1;
2434                         break;
2435                 case PBLEND_ADD:
2436                         alpha = p->alpha * colormultiplier[3];
2437                         // ensure alpha multiplier saturates properly
2438                         if (alpha > 1.0f)
2439                                 alpha = 1.0f;
2440                         // additive and modulate can just fade out in fog (this is correct)
2441                         if (r_refdef.fogenabled)
2442                                 alpha *= RSurf_FogVertex(p->org);
2443                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2444                         c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2445                         c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2446                         c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2447                         c4f[3] = 1;
2448                         break;
2449                 case PBLEND_ALPHA:
2450                         c4f[0] = p->color[0] * colormultiplier[0];
2451                         c4f[1] = p->color[1] * colormultiplier[1];
2452                         c4f[2] = p->color[2] * colormultiplier[2];
2453                         c4f[3] = p->alpha * colormultiplier[3];
2454                         // note: lighting is not cheap!
2455                         if (particletype[p->typeindex].lighting)
2456                         {
2457                                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2458                                 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2459                                 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2460                                 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2461                         }
2462                         // mix in the fog color
2463                         if (r_refdef.fogenabled)
2464                         {
2465                                 fog = RSurf_FogVertex(p->org);
2466                                 ifog = 1 - fog;
2467                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2468                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2469                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2470                         }
2471                         break;
2472                 }
2473                 // copy the color into the other three vertices
2474                 Vector4Copy(c4f, c4f + 4);
2475                 Vector4Copy(c4f, c4f + 8);
2476                 Vector4Copy(c4f, c4f + 12);
2477
2478                 size = p->size * cl_particles_size.value;
2479                 tex = &particletexture[p->texnum];
2480                 switch(p->orientation)
2481                 {
2482                 case PARTICLE_INVALID:
2483                 case PARTICLE_BILLBOARD:
2484                         VectorScale(r_refdef.view.left, -size * p->stretch, right);
2485                         VectorScale(r_refdef.view.up, size, up);
2486                         v3f[ 0] = p->org[0] - right[0] - up[0];
2487                         v3f[ 1] = p->org[1] - right[1] - up[1];
2488                         v3f[ 2] = p->org[2] - right[2] - up[2];
2489                         v3f[ 3] = p->org[0] - right[0] + up[0];
2490                         v3f[ 4] = p->org[1] - right[1] + up[1];
2491                         v3f[ 5] = p->org[2] - right[2] + up[2];
2492                         v3f[ 6] = p->org[0] + right[0] + up[0];
2493                         v3f[ 7] = p->org[1] + right[1] + up[1];
2494                         v3f[ 8] = p->org[2] + right[2] + up[2];
2495                         v3f[ 9] = p->org[0] + right[0] - up[0];
2496                         v3f[10] = p->org[1] + right[1] - up[1];
2497                         v3f[11] = p->org[2] + right[2] - up[2];
2498                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2499                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2500                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2501                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2502                         break;
2503                 case PARTICLE_ORIENTED_DOUBLESIDED:
2504                         VectorVectors(p->vel, right, up);
2505                         VectorScale(right, size * p->stretch, right);
2506                         VectorScale(up, size, up);
2507                         v3f[ 0] = p->org[0] - right[0] - up[0];
2508                         v3f[ 1] = p->org[1] - right[1] - up[1];
2509                         v3f[ 2] = p->org[2] - right[2] - up[2];
2510                         v3f[ 3] = p->org[0] - right[0] + up[0];
2511                         v3f[ 4] = p->org[1] - right[1] + up[1];
2512                         v3f[ 5] = p->org[2] - right[2] + up[2];
2513                         v3f[ 6] = p->org[0] + right[0] + up[0];
2514                         v3f[ 7] = p->org[1] + right[1] + up[1];
2515                         v3f[ 8] = p->org[2] + right[2] + up[2];
2516                         v3f[ 9] = p->org[0] + right[0] - up[0];
2517                         v3f[10] = p->org[1] + right[1] - up[1];
2518                         v3f[11] = p->org[2] + right[2] - up[2];
2519                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2520                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2521                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2522                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2523                         break;
2524                 case PARTICLE_SPARK:
2525                         len = VectorLength(p->vel);
2526                         VectorNormalize2(p->vel, up);
2527                         lenfactor = p->stretch * 0.04 * len;
2528                         if(lenfactor < size * 0.5)
2529                                 lenfactor = size * 0.5;
2530                         VectorMA(p->org, -lenfactor, up, v);
2531                         VectorMA(p->org,  lenfactor, up, up2);
2532                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2533                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2534                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2535                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2536                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2537                         break;
2538                 case PARTICLE_VBEAM:
2539                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2540                         VectorSubtract(p->vel, p->org, up);
2541                         VectorNormalize(up);
2542                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2543                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2544                         t2f[0] = tex->s2;t2f[1] = v[0];
2545                         t2f[2] = tex->s1;t2f[3] = v[0];
2546                         t2f[4] = tex->s1;t2f[5] = v[1];
2547                         t2f[6] = tex->s2;t2f[7] = v[1];
2548                         break;
2549                 case PARTICLE_HBEAM:
2550                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2551                         VectorSubtract(p->vel, p->org, up);
2552                         VectorNormalize(up);
2553                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2554                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2555                         t2f[0] = v[0];t2f[1] = tex->t1;
2556                         t2f[2] = v[0];t2f[3] = tex->t2;
2557                         t2f[4] = v[1];t2f[5] = tex->t2;
2558                         t2f[6] = v[1];t2f[7] = tex->t1;
2559                         break;
2560                 }
2561         }
2562
2563         // now render batches of particles based on blendmode and texture
2564         blendmode = PBLEND_INVALID;
2565         texture = NULL;
2566         batchstart = 0;
2567         batchcount = 0;
2568         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2569         {
2570                 p = cl.particles + surfacelist[surfacelistindex];
2571
2572                 if (blendmode != p->blendmode)
2573                 {
2574                         blendmode = p->blendmode;
2575                         switch(blendmode)
2576                         {
2577                         case PBLEND_ALPHA:
2578                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2579                                 break;
2580                         case PBLEND_INVALID:
2581                         case PBLEND_ADD:
2582                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2583                                 break;
2584                         case PBLEND_INVMOD:
2585                                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2586                                 break;
2587                         }
2588                 }
2589                 if (texture != particletexture[p->texnum].texture)
2590                 {
2591                         texture = particletexture[p->texnum].texture;
2592                         R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2593                 }
2594
2595                 // iterate until we find a change in settings
2596                 batchstart = surfacelistindex++;
2597                 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2598                 {
2599                         p = cl.particles + surfacelist[surfacelistindex];
2600                         if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2601                                 break;
2602                 }
2603
2604                 batchcount = surfacelistindex - batchstart;
2605                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2606         }
2607 }
2608
2609 void R_DrawParticles (void)
2610 {
2611         int i, a;
2612         int drawparticles = r_drawparticles.integer;
2613         float minparticledist;
2614         particle_t *p;
2615         float gravity, frametime, f, dist, oldorg[3];
2616         float drawdist2;
2617         int hitent;
2618         trace_t trace;
2619         qboolean update;
2620
2621         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2622         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2623
2624         // LordHavoc: early out conditions
2625         if (!cl.num_particles)
2626                 return;
2627
2628         minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2629         gravity = frametime * cl.movevars_gravity;
2630         update = frametime > 0;
2631         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2632         drawdist2 = drawdist2*drawdist2;
2633
2634         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2635         {
2636                 if (!p->typeindex)
2637                 {
2638                         if (cl.free_particle > i)
2639                                 cl.free_particle = i;
2640                         continue;
2641                 }
2642
2643                 if (update)
2644                 {
2645                         if (p->delayedspawn > cl.time)
2646                                 continue;
2647                         p->delayedspawn = 0;
2648
2649                         p->size += p->sizeincrease * frametime;
2650                         p->alpha -= p->alphafade * frametime;
2651
2652                         if (p->alpha <= 0 || p->die <= cl.time)
2653                                 goto killparticle;
2654
2655                         if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2656                         {
2657                                 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2658                                 {
2659                                         if (p->typeindex == pt_blood)
2660                                                 p->size += frametime * 8;
2661                                         else
2662                                                 p->vel[2] -= p->gravity * gravity;
2663                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2664                                         VectorScale(p->vel, f, p->vel);
2665                                 }
2666                                 else
2667                                 {
2668                                         p->vel[2] -= p->gravity * gravity;
2669                                         if (p->airfriction)
2670                                         {
2671                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2672                                                 VectorScale(p->vel, f, p->vel);
2673                                         }
2674                                 }
2675
2676                                 VectorCopy(p->org, oldorg);
2677                                 VectorMA(p->org, frametime, p->vel, p->org);
2678                                 if (p->bounce && cl.time >= p->delayedcollisions)
2679                                 {
2680                                         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);
2681                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2682                                         // or if the trace hit something flagged as NOIMPACT
2683                                         // then remove the particle
2684                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2685                                                 goto killparticle;
2686                                         VectorCopy(trace.endpos, p->org);
2687                                         // react if the particle hit something
2688                                         if (trace.fraction < 1)
2689                                         {
2690                                                 VectorCopy(trace.endpos, p->org);
2691
2692                                                 if (p->staintexnum >= 0)
2693                                                 {
2694                                                         // blood - splash on solid
2695                                                         if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2696                                                         {
2697                                                                 R_Stain(p->org, 16,
2698                                                                         (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2699                                                                         (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2700                                                                 if (cl_decals.integer)
2701                                                                 {
2702                                                                         // create a decal for the blood splat
2703                                                                         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!
2704                                                                 }
2705                                                         }
2706                                                 }
2707
2708                                                 if (p->typeindex == pt_blood)
2709                                                 {
2710                                                         // blood - splash on solid
2711                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2712                                                                 goto killparticle;
2713                                                         if(p->staintexnum == -1) // staintex < -1 means no stains at all
2714                                                         {
2715                                                                 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)));
2716                                                                 if (cl_decals.integer)
2717                                                                 {
2718                                                                         // create a decal for the blood splat
2719                                                                         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);
2720                                                                 }
2721                                                         }
2722                                                         goto killparticle;
2723                                                 }
2724                                                 else if (p->bounce < 0)
2725                                                 {
2726                                                         // bounce -1 means remove on impact
2727                                                         goto killparticle;
2728                                                 }
2729                                                 else
2730                                                 {
2731                                                         // anything else - bounce off solid
2732                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2733                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2734                                                         if (DotProduct(p->vel, p->vel) < 0.03)
2735                                                                 VectorClear(p->vel);
2736                                                 }
2737                                         }
2738                                 }
2739                         }
2740
2741                         if (p->typeindex != pt_static)
2742                         {
2743                                 switch (p->typeindex)
2744                                 {
2745                                 case pt_entityparticle:
2746                                         // particle that removes itself after one rendered frame
2747                                         if (p->time2)
2748                                                 goto killparticle;
2749                                         else
2750                                                 p->time2 = 1;
2751                                         break;
2752                                 case pt_blood:
2753                                         a = CL_PointSuperContents(p->org);
2754                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2755                                                 goto killparticle;
2756                                         break;
2757                                 case pt_bubble:
2758                                         a = CL_PointSuperContents(p->org);
2759                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2760                                                 goto killparticle;
2761                                         break;
2762                                 case pt_rain:
2763                                         a = CL_PointSuperContents(p->org);
2764                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2765                                                 goto killparticle;
2766                                         break;
2767                                 case pt_snow:
2768                                         if (cl.time > p->time2)
2769                                         {
2770                                                 // snow flutter
2771                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
2772                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2773                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2774                                         }
2775                                         a = CL_PointSuperContents(p->org);
2776                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2777                                                 goto killparticle;
2778                                         break;
2779                                 default:
2780                                         break;
2781                                 }
2782                         }
2783                 }
2784                 else if (p->delayedspawn)
2785                         continue;
2786                 if (!drawparticles)
2787                         continue;
2788                 // don't render particles too close to the view (they chew fillrate)
2789                 // also don't render particles behind the view (useless)
2790                 // further checks to cull to the frustum would be too slow here
2791                 switch(p->typeindex)
2792                 {
2793                 case pt_beam:
2794                         // beams have no culling
2795                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2796                         break;
2797                 default:
2798                         if(cl_particles_visculling.integer)
2799                                 if (!r_refdef.viewcache.world_novis)
2800                                         if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2801                                         {
2802                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2803                                                 if(leaf)
2804                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2805                                                                 continue;
2806                                         }
2807                         // anything else just has to be in front of the viewer and visible at this distance
2808                         if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2809                                 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2810                         break;
2811                 }
2812
2813                 continue;
2814 killparticle:
2815                 p->typeindex = 0;
2816                 if (cl.free_particle > i)
2817                         cl.free_particle = i;
2818         }
2819
2820         // reduce cl.num_particles if possible
2821         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2822                 cl.num_particles--;
2823
2824         if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2825         {
2826                 particle_t *oldparticles = cl.particles;
2827                 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2828                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2829                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2830                 Mem_Free(oldparticles);
2831         }
2832 }