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