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