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