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