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