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