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