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