]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
sys: work around incomplete POSIX support in MacOS
[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->airfriction = pairfriction;
797         part->liquidfriction = pliquidfriction;
798         part->die = cl.time + lifetime;
799         part->delayedspawn = cl.time;
800 //      part->delayedcollisions = 0;
801         part->qualityreduction = pqualityreduction;
802         part->angle = angle;
803         part->spin = spin;
804         // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
805         if (part->typeindex == pt_rain)
806         {
807                 int i;
808                 particle_t *part2;
809                 vec3_t endvec;
810                 trace_t trace;
811                 // turn raindrop into simple spark and create delayedspawn splash effect
812                 part->typeindex = pt_spark;
813                 part->bounce = 0;
814                 VectorMA(part->org, lifetime, part->vel, endvec);
815                 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
816                 part->die = cl.time + lifetime * trace.fraction;
817                 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);
818                 if (part2)
819                 {
820                         part2->delayedspawn = part->die;
821                         part2->die += part->die - cl.time;
822                         for (i = rand() & 7;i < 10;i++)
823                         {
824                                 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);
825                                 if (part2)
826                                 {
827                                         part2->delayedspawn = part->die;
828                                         part2->die += part->die - cl.time;
829                                 }
830                         }
831                 }
832         }
833         else if (part->typeindex == pt_explode || part->typeindex == pt_explode2)
834                 part->time2 = rand()&3; // time2 is used to progress the colour ramp index
835
836 #if 0
837         else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
838         {
839                 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
840                 vec3_t endvec;
841                 trace_t trace;
842                 VectorMA(part->org, lifetime, part->vel, endvec);
843                 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
844                 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
845         }
846 #endif
847
848         return part;
849 }
850
851
852
853 /**
854  * @brief      Creates a simple particle, a square like Quake, or a disc like GLQuake
855  *
856  * @param[in]  origin                                                 ?
857  * @param[in]  color_1,color_2                                        Minimum and maximum range of color, randomly interpolated with pcolor2 to decide particle color
858  * @param[in]  gravity                                                How much effect gravity has on the particle (0-1)
859  * @param[in]  offset_x,offset_y,offset_z                             Starting origin of particle
860  * @param[in]  velocity_offset_x,velocity_offset_y,velocity_offset_z  Starting velocity of particle
861  * @param[in]  air_friction                                           How much the particle slows down, in air, per second (0-1 typically, can slowdown faster than 1)
862  * @param[in]  liquid_friction                                        How much the particle slows down, in liquids, per second (0-1 typically, can slowdown faster than 1)
863  * @param[in]  origin_jitter                                          ?
864  * @param[in]  velocity_jitter                                        ?
865  * @param[in]  lifetime                                               How long the particle can live (note it is also removed if alpha drops to nothing)
866  *
867  * @return     Pointer to the new particle
868  */
869 particle_t *CL_NewQuakeParticle(
870         const vec3_t origin,
871         const unsigned short ptypeindex,
872         const int color_1,
873         const int color_2,
874         const float gravity,
875         const float offset_x,
876         const float offset_y,
877         const float offset_z,
878         const float velocity_offset_x,
879         const float velocity_offset_y,
880         const float velocity_offset_z,
881         const float air_friction,
882         const float liquid_friction,
883         const float origin_jitter,
884         const float velocity_jitter,
885         const float lifetime)
886 {
887         int texture;
888
889         // Set the particle texture based on the value of cl_particles_quake; defaulting to the GLQuake disc
890         if (cl_particles_quake.integer == 2)
891                 texture = tex_square;
892         else
893                 texture = tex_particle;
894
895         return CL_NewParticle(
896                 origin,
897                 ptypeindex,          // type
898                 color_1,
899                 color_2,
900                 texture,
901                 0.8f,                // size
902                 0,                   // size increase
903                 255,                 // alpha
904                 0,                   // alpha fade
905                 gravity,
906                 0,                   // bounce
907                 offset_x,
908                 offset_y,
909                 offset_z,
910                 velocity_offset_x,
911                 velocity_offset_y,
912                 velocity_offset_z,
913                 air_friction,
914                 liquid_friction,
915                 origin_jitter,
916                 velocity_jitter,
917                 true,                // quality reduction
918                 lifetime,
919                 1,                   // stretch
920                 PBLEND_ALPHA,        // blend mode
921                 PARTICLE_BILLBOARD,  // orientation
922                 -1,                  // stain color 1
923                 -1,                  // stain color 2
924                 -1,                  // stain texture
925                 1,                   // stain alpha
926                 1,                   // stain size
927                 0,                   // angle
928                 0,                   // spin
929                 NULL                 // tint
930         );
931 }
932
933
934
935 static void CL_ImmediateBloodStain(particle_t *part)
936 {
937         vec3_t v;
938         int staintex;
939
940         // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
941         if (part->staintexnum >= 0 && cl_decals.integer)
942         {
943                 VectorCopy(part->vel, v);
944                 VectorNormalize(v);
945                 staintex = part->staintexnum;
946                 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);
947         }
948
949         // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
950         if (part->typeindex == pt_blood && cl_decals.integer)
951         {
952                 VectorCopy(part->vel, v);
953                 VectorNormalize(v);
954                 staintex = tex_blooddecal[rand()&7];
955                 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);
956         }
957 }
958
959 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
960 {
961         int l1, l2;
962         entity_render_t *ent = &cl.entities[hitent].render;
963         unsigned char color[3];
964         if (!cl_decals.integer)
965                 return;
966         if (!ent->allowdecals)
967                 return;
968
969         l2 = (int)lhrandom(0.5, 256.5);
970         l1 = 256 - l2;
971         color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
972         color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
973         color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
974
975         if (vid.sRGB3D)
976                 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);
977         else
978                 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);
979 }
980
981 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
982 {
983         int i;
984         vec_t bestfrac;
985         vec3_t bestorg;
986         vec3_t bestnormal;
987         vec3_t org2;
988         int besthitent = 0, hitent;
989         trace_t trace;
990         bestfrac = 10;
991         for (i = 0;i < 32;i++)
992         {
993                 VectorRandom(org2);
994                 VectorMA(org, maxdist, org2, org2);
995                 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, 0, collision_extendmovelength.value, true, false, &hitent, false, true);
996                 // take the closest trace result that doesn't end up hitting a NOMARKS
997                 // surface (sky for example)
998                 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
999                 {
1000                         bestfrac = trace.fraction;
1001                         besthitent = hitent;
1002                         VectorCopy(trace.endpos, bestorg);
1003                         VectorCopy(trace.plane.normal, bestnormal);
1004                 }
1005         }
1006         if (bestfrac < 1)
1007                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
1008 }
1009
1010 // generates a cubemap name with prefix flags based on info flags (for now only `!`)
1011 static char *LightCubemapNumToName(char *vabuf, size_t vasize, int lightcubemapnum, int flags)
1012 {
1013         if (lightcubemapnum <= 0)
1014                 return NULL;
1015         // `!` is prepended if the cubemap must be nearest-filtered
1016         if (flags & PARTICLEEFFECT_FORCENEAREST)
1017                 return va(vabuf, vasize, "!cubemaps/%i", lightcubemapnum);
1018         return va(vabuf, vasize, "cubemaps/%i", lightcubemapnum);
1019 }
1020
1021 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
1022 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
1023 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);
1024 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)
1025 {
1026         vec3_t center;
1027         matrix4x4_t lightmatrix;
1028         particle_t *part;
1029
1030         VectorLerp(originmins, 0.5, originmaxs, center);
1031         Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
1032         if (effectnameindex == EFFECT_SVC_PARTICLE)
1033         {
1034                 if (cl_particles.integer)
1035                 {
1036                         // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
1037                         if (count == 1024)
1038                                 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1039                         else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
1040                                 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1041                         else
1042                         {
1043                                 count *= cl_particles_quality.value;
1044                                 for (;count > 0;count--)
1045                                 {
1046                                         int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
1047                                         CL_NewQuakeParticle(
1048                                                 center,                                      // origin
1049                                                 pt_alphastatic,                              // type
1050                                                 k,                                           // color 1
1051                                                 k,                                           // color 2
1052                                                 0.15,                                        // gravity
1053                                                 lhrandom(originmins[0], originmaxs[0]),      // offset x
1054                                                 lhrandom(originmins[1], originmaxs[1]),      // offset y
1055                                                 lhrandom(originmins[2], originmaxs[2]),      // offset z
1056                                                 lhrandom(velocitymins[0], velocitymaxs[0]),  // velocity offset x
1057                                                 lhrandom(velocitymins[1], velocitymaxs[1]),  // velocity offset y
1058                                                 lhrandom(velocitymins[2], velocitymaxs[2]),  // velocity offset z
1059                                                 0,                                           // air friction
1060                                                 0,                                           // liquid friction
1061                                                 8,                                           // origin jitter
1062                                                 3,                                           // velocity jitter
1063                                                 lhrandom(0.1, 0.4)                           // lifetime
1064                                         );
1065                                 }
1066                         }
1067                 }
1068         }
1069         else if (effectnameindex == EFFECT_TE_WIZSPIKE)
1070                 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1071         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
1072                 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1073         else if (effectnameindex == EFFECT_TE_SPIKE)
1074         {
1075                 if (cl_particles_bulletimpacts.integer)
1076                 {
1077                         if (cl_particles_quake.integer)
1078                         {
1079                                 if (cl_particles_smoke.integer)
1080                                         CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1081                         }
1082                         else
1083                         {
1084                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1085                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
1086                                 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);
1087                         }
1088                 }
1089                 // bullet hole
1090                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1091                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1092         }
1093         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
1094         {
1095                 if (cl_particles_bulletimpacts.integer)
1096                 {
1097                         if (cl_particles_quake.integer)
1098                         {
1099                                 if (cl_particles_smoke.integer)
1100                                         CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1101                         }
1102                         else
1103                         {
1104                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1105                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
1106                                 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1107                         }
1108                 }
1109                 // bullet hole
1110                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1111                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1112                 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1113         }
1114         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
1115         {
1116                 if (cl_particles_bulletimpacts.integer)
1117                 {
1118                         if (cl_particles_quake.integer)
1119                         {
1120                                 if (cl_particles_smoke.integer)
1121                                         CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1122                         }
1123                         else
1124                         {
1125                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1126                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1127                                 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);
1128                         }
1129                 }
1130                 // bullet hole
1131                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1132                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1133         }
1134         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1135         {
1136                 if (cl_particles_bulletimpacts.integer)
1137                 {
1138                         if (cl_particles_quake.integer)
1139                         {
1140                                 if (cl_particles_smoke.integer)
1141                                         CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1142                         }
1143                         else
1144                         {
1145                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1146                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1147                                 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);
1148                         }
1149                 }
1150                 // bullet hole
1151                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1152                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1153                 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);
1154         }
1155         else if (effectnameindex == EFFECT_TE_BLOOD)
1156         {
1157                 if (!cl_particles_blood.integer)
1158                         return;
1159                 if (cl_particles_quake.integer)
1160                         CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1161                 else
1162                 {
1163                         static double bloodaccumulator = 0;
1164                         qbool immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1165                         //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);
1166                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1167                         for (;bloodaccumulator > 0;bloodaccumulator--)
1168                         {
1169                                 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);
1170                                 if (immediatebloodstain && part)
1171                                 {
1172                                         immediatebloodstain = false;
1173                                         CL_ImmediateBloodStain(part);
1174                                 }
1175                         }
1176                 }
1177         }
1178         else if (effectnameindex == EFFECT_TE_SPARK)
1179                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1180         else if (effectnameindex == EFFECT_TE_PLASMABURN)
1181         {
1182                 // plasma scorch mark
1183                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1184                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1185                 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1186         }
1187         else if (effectnameindex == EFFECT_TE_GUNSHOT)
1188         {
1189                 if (cl_particles_bulletimpacts.integer)
1190                 {
1191                         if (cl_particles_quake.integer)
1192                                 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1193                         else
1194                         {
1195                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1196                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1197                                 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);
1198                         }
1199                 }
1200                 // bullet hole
1201                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1202                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1203         }
1204         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1205         {
1206                 if (cl_particles_bulletimpacts.integer)
1207                 {
1208                         if (cl_particles_quake.integer)
1209                                 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1210                         else
1211                         {
1212                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1213                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1214                                 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);
1215                         }
1216                 }
1217                 // bullet hole
1218                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1219                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1220                 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);
1221         }
1222         else if (effectnameindex == EFFECT_TE_EXPLOSION)
1223         {
1224                 CL_ParticleExplosion(center);
1225                 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);
1226         }
1227         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1228         {
1229                 CL_ParticleExplosion(center);
1230                 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);
1231         }
1232         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1233         {
1234                 if (cl_particles_quake.integer)
1235                 {
1236                         int i;
1237                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1238                         {
1239                                 if (i & 1)
1240                                         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);
1241                                 else
1242                                         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);
1243                         }
1244                 }
1245                 else
1246                         CL_ParticleExplosion(center);
1247                 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);
1248         }
1249         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1250                 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);
1251         else if (effectnameindex == EFFECT_TE_FLAMEJET)
1252         {
1253                 count *= cl_particles_quality.value;
1254                 while (count-- > 0)
1255                         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);
1256         }
1257         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1258         {
1259                 float i, j, inc, vel;
1260                 vec3_t dir, org;
1261
1262                 inc = 8 / cl_particles_quality.value;
1263                 for (i = -128;i < 128;i += inc)
1264                 {
1265                         for (j = -128;j < 128;j += inc)
1266                         {
1267                                 dir[0] = j + lhrandom(0, inc);
1268                                 dir[1] = i + lhrandom(0, inc);
1269                                 dir[2] = 256;
1270                                 org[0] = center[0] + dir[0];
1271                                 org[1] = center[1] + dir[1];
1272                                 org[2] = center[2] + lhrandom(0, 64);
1273                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1274                                 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);
1275                         }
1276                 }
1277         }
1278         else if (effectnameindex == EFFECT_TE_TELEPORT)
1279         {
1280                 float i, j, k, inc, vel;
1281                 vec3_t dir;
1282
1283                 if (cl_particles_quake.integer)
1284                         inc = 4 / cl_particles_quality.value;
1285                 else
1286                         inc = 8 / cl_particles_quality.value;
1287                 for (i = -16;i < 16;i += inc)
1288                 {
1289                         for (j = -16;j < 16;j += inc)
1290                         {
1291                                 for (k = -24;k < 32;k += inc)
1292                                 {
1293                                         VectorSet(dir, i*8, j*8, k*8);
1294                                         VectorNormalize(dir);
1295                                         vel = lhrandom(50, 113);
1296                                         if (cl_particles_quake.integer)
1297                                                 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);
1298                                         else
1299                                                 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);
1300                                 }
1301                         }
1302                 }
1303                 if (!cl_particles_quake.integer)
1304                         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);
1305                 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);
1306         }
1307         else if (effectnameindex == EFFECT_TE_TEI_G3)
1308                 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);
1309         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1310         {
1311                 if (cl_particles_smoke.integer)
1312                 {
1313                         count *= 0.25f * cl_particles_quality.value;
1314                         while (count-- > 0)
1315                                 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);
1316                 }
1317         }
1318         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1319         {
1320                 CL_ParticleExplosion(center);
1321                 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);
1322         }
1323         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1324         {
1325                 float f;
1326                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1327                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1328                 if (cl_particles_smoke.integer)
1329                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1330                                 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);
1331                 if (cl_particles_sparks.integer)
1332                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1333                                 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);
1334                 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);
1335         }
1336         else if (effectnameindex == EFFECT_EF_FLAME)
1337         {
1338                 if (!spawnparticles)
1339                         count = 0;
1340                 count *= 300 * cl_particles_quality.value;
1341                 while (count-- > 0)
1342                         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);
1343                 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);
1344         }
1345         else if (effectnameindex == EFFECT_EF_STARDUST)
1346         {
1347                 if (!spawnparticles)
1348                         count = 0;
1349                 count *= 200 * cl_particles_quality.value;
1350                 while (count-- > 0)
1351                         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);
1352                 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);
1353         }
1354         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1355         {
1356                 vec3_t dir, pos;
1357                 float len, dec, qd;
1358                 int smoke, blood, bubbles, r, color, spawnedcount;
1359
1360                 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1361                 {
1362                         vec4_t light;
1363                         Vector4Set(light, 0, 0, 0, 0);
1364
1365                         if (effectnameindex == EFFECT_TR_ROCKET)
1366                                 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1367                         else if (effectnameindex == EFFECT_TR_VORESPIKE)
1368                         {
1369                                 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1370                                         Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1371                                 else
1372                                         Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1373                         }
1374                         else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1375                                 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1376
1377                         if (light[3])
1378                         {
1379                                 matrix4x4_t traillightmatrix;
1380                                 Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1381                                 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);
1382                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1383                         }
1384                 }
1385
1386                 if (!spawnparticles)
1387                         return;
1388
1389                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1390                         return;
1391
1392                 VectorSubtract(originmaxs, originmins, dir);
1393                 len = VectorNormalizeLength(dir);
1394
1395                 if (ent)
1396                 {
1397                         dec = -ent->persistent.trail_time;
1398                         ent->persistent.trail_time += len;
1399                         if (ent->persistent.trail_time < 0.01f)
1400                                 return;
1401
1402                         // if we skip out, leave it reset
1403                         ent->persistent.trail_time = 0.0f;
1404                 }
1405                 else
1406                         dec = 0;
1407
1408                 // advance into this frame to reach the first puff location
1409                 VectorMA(originmins, dec, dir, pos);
1410                 len -= dec;
1411
1412                 smoke = cl_particles.integer && cl_particles_smoke.integer;
1413                 blood = cl_particles.integer && cl_particles_blood.integer;
1414                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1415                 qd = 1.0f / cl_particles_quality.value;
1416                 spawnedcount = 0;
1417
1418                 while (len >= 0 && ++spawnedcount <= 16384)
1419                 {
1420                         dec = 3;
1421                         if (blood)
1422                         {
1423                                 if (effectnameindex == EFFECT_TR_BLOOD)
1424                                 {
1425                                         if (cl_particles_quake.integer)
1426                                         {
1427                                                 color = particlepalette[67 + (rand()&3)];
1428                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0.25, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 2);
1429                                         }
1430                                         else
1431                                         {
1432                                                 dec = 16;
1433                                                 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);
1434                                         }
1435                                 }
1436                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1437                                 {
1438                                         if (cl_particles_quake.integer)
1439                                         {
1440                                                 dec = 6;
1441                                                 color = particlepalette[67 + (rand()&3)];
1442                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0.25, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 2);
1443                                         }
1444                                         else
1445                                         {
1446                                                 dec = 32;
1447                                                 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);
1448                                         }
1449                                 }
1450                         }
1451                         if (smoke)
1452                         {
1453                                 if (effectnameindex == EFFECT_TR_ROCKET)
1454                                 {
1455                                         if (cl_particles_quake.integer)
1456                                         {
1457                                                 r = rand()&3;
1458                                                 color = particlepalette[ramp3[r]];
1459                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, -0.10, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 0.1372549 * (6 - r));
1460                                         }
1461                                         else
1462                                         {
1463                                                 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);
1464                                                 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);
1465                                         }
1466                                 }
1467                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1468                                 {
1469                                         if (cl_particles_quake.integer)
1470                                         {
1471                                                 r = 2 + (rand()%4);
1472                                                 color = particlepalette[ramp3[r]];
1473                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, -0.15, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, 0.1372549 * (6 - r));
1474                                         }
1475                                         else
1476                                         {
1477                                                 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);
1478                                         }
1479                                 }
1480                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1481                                 {
1482                                         if (cl_particles_quake.integer)
1483                                         {
1484                                                 dec = 6;
1485                                                 color = particlepalette[52 + (rand()&7)];
1486                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, 0.5);
1487                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, 0.5);
1488                                         }
1489                                         else if (gamemode == GAME_GOODVSBAD2)
1490                                         {
1491                                                 dec = 6;
1492                                                 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);
1493                                         }
1494                                         else
1495                                         {
1496                                                 color = particlepalette[20 + (rand()&7)];
1497                                                 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);
1498                                         }
1499                                 }
1500                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1501                                 {
1502                                         if (cl_particles_quake.integer)
1503                                         {
1504                                                 dec = 6;
1505                                                 color = particlepalette[230 + (rand()&7)];
1506                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30 *  dir[1], 30 * -dir[0], 0, 0, 0, 0, 0, 0.5);
1507                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 30 * -dir[1], 30 *  dir[0], 0, 0, 0, 0, 0, 0.5);
1508                                         }
1509                                         else
1510                                         {
1511                                                 color = particlepalette[226 + (rand()&7)];
1512                                                 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);
1513                                         }
1514                                 }
1515                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1516                                 {
1517                                         if (cl_particles_quake.integer)
1518                                         {
1519                                                 color = particlepalette[152 + (rand()&3)];
1520                                                 CL_NewQuakeParticle(center, pt_alphastatic, color, color, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, 0.3);
1521                                         }
1522                                         else if (gamemode == GAME_GOODVSBAD2)
1523                                         {
1524                                                 dec = 6;
1525                                                 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);
1526                                         }
1527                                         else if (gamemode == GAME_PRYDON)
1528                                         {
1529                                                 dec = 6;
1530                                                 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);
1531                                         }
1532                                         else
1533                                                 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);
1534                                 }
1535                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1536                                 {
1537                                         dec = 7;
1538                                         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);
1539                                 }
1540                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1541                                 {
1542                                         dec = 4;
1543                                         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);
1544                                 }
1545                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1546                                         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);
1547                         }
1548                         if (bubbles)
1549                         {
1550                                 if (effectnameindex == EFFECT_TR_ROCKET)
1551                                         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);
1552                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1553                                         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);
1554                         }
1555                         // advance to next time and position
1556                         dec *= qd;
1557                         len -= dec;
1558                         VectorMA (pos, dec, dir, pos);
1559                 }
1560                 if (ent)
1561                         ent->persistent.trail_time = len;
1562         }
1563         else
1564                 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1565 }
1566
1567 // this is also called on point effects with spawndlight = true and
1568 // spawnparticles = true
1569 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)
1570 {
1571         qbool found = false;
1572         char vabuf[1024];
1573         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1574         {
1575                 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1576                 return; // no such effect
1577         }
1578         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1579         {
1580                 int effectinfoindex;
1581                 int supercontents;
1582                 int tex, staintex;
1583                 particleeffectinfo_t *info;
1584                 vec3_t center;
1585                 vec3_t traildir;
1586                 vec3_t trailpos;
1587                 vec3_t rvec;
1588                 vec3_t angles;
1589                 vec3_t velocity;
1590                 vec3_t forward;
1591                 vec3_t right;
1592                 vec3_t up;
1593                 vec_t traillen;
1594                 vec_t trailstep;
1595                 qbool underwater;
1596                 qbool immediatebloodstain;
1597                 particle_t *part;
1598                 float avgtint[4], tint[4], tintlerp;
1599                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1600                 VectorLerp(originmins, 0.5, originmaxs, center);
1601                 supercontents = CL_PointSuperContents(center);
1602                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1603                 VectorSubtract(originmaxs, originmins, traildir);
1604                 traillen = VectorLength(traildir);
1605                 VectorNormalize(traildir);
1606                 if(tintmins)
1607                 {
1608                         Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1609                 }
1610                 else
1611                 {
1612                         Vector4Set(avgtint, 1, 1, 1, 1);
1613                 }
1614                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1615                 {
1616                         if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1617                         {
1618                                 qbool definedastrail = info->trailspacing > 0;
1619
1620                                 qbool drawastrail = wanttrail;
1621                                 if (cl_particles_forcetraileffects.integer)
1622                                         drawastrail = drawastrail || definedastrail;
1623
1624                                 found = true;
1625                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1626                                         continue;
1627                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1628                                         continue;
1629
1630                                 // spawn a dlight if requested
1631                                 if (info->lightradiusstart > 0 && spawndlight)
1632                                 {
1633                                         matrix4x4_t tempmatrix;
1634                                         if (drawastrail)
1635                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1636                                         else
1637                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1638                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1639                                         {
1640                                                 // light flash (explosion, etc)
1641                                                 // called when effect starts
1642                                                 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);
1643                                         }
1644                                         else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1645                                         {
1646                                                 // glowing entity
1647                                                 // called by CL_LinkNetworkEntity
1648                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1649                                                 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1650                                                 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1651                                                 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1652                                                 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);
1653                                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1654                                         }
1655                                 }
1656
1657                                 if (!spawnparticles)
1658                                         continue;
1659
1660                                 // spawn particles
1661                                 tex = info->tex[0];
1662                                 if (info->tex[1] > info->tex[0])
1663                                 {
1664                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1665                                         tex = min(tex, info->tex[1] - 1);
1666                                 }
1667                                 if(info->staintex[0] < 0)
1668                                         staintex = info->staintex[0];
1669                                 else
1670                                 {
1671                                         staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1672                                         staintex = min(staintex, info->staintex[1] - 1);
1673                                 }
1674                                 if (info->particletype == pt_decal)
1675                                 {
1676                                         VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1677                                         AnglesFromVectors(angles, velocity, NULL, false);
1678                                         AngleVectors(angles, forward, right, up);
1679                                         VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1680
1681                                         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]);
1682                                 }
1683                                 else if (info->orientation == PARTICLE_HBEAM)
1684                                 {
1685                                         if (!drawastrail)
1686                                                 continue;
1687
1688                                         AnglesFromVectors(angles, traildir, NULL, false);
1689                                         AngleVectors(angles, forward, right, up);
1690                                         VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1691
1692                                         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);
1693                                 }
1694                                 else
1695                                 {
1696                                         float cnt;
1697                                         if (!cl_particles.integer)
1698                                                 continue;
1699                                         switch (info->particletype)
1700                                         {
1701                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1702                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1703                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1704                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1705                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1706                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1707                                         default: break;
1708                                         }
1709
1710                                         cnt = info->countabsolute;
1711                                         cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1712                                         // if drawastrail is not set, we will
1713                                         // use the regular cnt-based random
1714                                         // particle spawning at the center; so
1715                                         // do NOT apply trailspacing then!
1716                                         if (drawastrail && definedastrail)
1717                                                 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1718                                         cnt *= fade;
1719                                         if (cnt == 0)
1720                                                 continue;  // nothing to draw
1721                                         info->particleaccumulator += cnt;
1722
1723                                         if (drawastrail || definedastrail)
1724                                                 immediatebloodstain = false;
1725                                         else
1726                                                 immediatebloodstain =
1727                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1728                                                         ||
1729                                                         ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1730
1731                                         if (drawastrail)
1732                                         {
1733                                                 VectorCopy(originmins, trailpos);
1734                                                 trailstep = traillen / cnt;
1735                                         }
1736                                         else
1737                                         {
1738                                                 VectorCopy(center, trailpos);
1739                                                 trailstep = 0;
1740                                         }
1741
1742                                         if (trailstep == 0)
1743                                         {
1744                                                 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1745                                                 AnglesFromVectors(angles, velocity, NULL, false);
1746                                         }
1747                                         else
1748                                                 AnglesFromVectors(angles, traildir, NULL, false);
1749
1750                                         AngleVectors(angles, forward, right, up);
1751                                         VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1752                                         VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1753                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1754                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1755                                         {
1756                                                 if (info->tex[1] > info->tex[0])
1757                                                 {
1758                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1759                                                         tex = min(tex, info->tex[1] - 1);
1760                                                 }
1761                                                 if (!(drawastrail || definedastrail))
1762                                                 {
1763                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1764                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1765                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1766                                                 }
1767                                                 if(tintmins)
1768                                                 {
1769                                                         tintlerp = lhrandom(0, 1);
1770                                                         Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1771                                                 }
1772                                                 VectorRandom(rvec);
1773                                                 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);
1774                                                 if (immediatebloodstain && part)
1775                                                 {
1776                                                         immediatebloodstain = false;
1777                                                         CL_ImmediateBloodStain(part);
1778                                                 }
1779                                                 if (trailstep)
1780                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1781                                         }
1782                                 }
1783                         }
1784                 }
1785         }
1786         if (!found)
1787                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1788 }
1789
1790 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)
1791 {
1792         CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1793 }
1794
1795 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)
1796 {
1797         CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1798 }
1799
1800 // note: this one ONLY does boxes!
1801 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)
1802 {
1803         CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1804 }
1805
1806 /*
1807 ===============
1808 CL_EntityParticles
1809 ===============
1810 */
1811 void CL_EntityParticles (const entity_t *ent)
1812 {
1813         int i, j;
1814         vec_t pitch, yaw, dist = 64, beamlength = 16;
1815         vec3_t org, v;
1816         static vec3_t avelocities[NUMVERTEXNORMALS];
1817         if (!cl_particles.integer) return;
1818         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1819
1820         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1821
1822         if (!avelocities[0][0])
1823                 for (i = 0;i < NUMVERTEXNORMALS;i++)
1824                         for (j = 0;j < 3;j++)
1825                                 avelocities[i][j] = lhrandom(0, 2.55);
1826
1827         for (i = 0;i < NUMVERTEXNORMALS;i++)
1828         {
1829                 yaw = cl.time * avelocities[i][0];
1830                 pitch = cl.time * avelocities[i][1];
1831                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1832                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1833                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1834                 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);
1835         }
1836 }
1837
1838
1839 void CL_ReadPointFile_f(cmd_state_t *cmd)
1840 {
1841         double org[3], leakorg[3];
1842         vec3_t vecorg;
1843         int r, c, s;
1844         char *pointfile = NULL, *pointfilepos, *t, tchar;
1845         char name[MAX_QPATH];
1846
1847         if (!cl.worldmodel)
1848                 return;
1849
1850         dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1851         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1852         if (!pointfile)
1853         {
1854                 Con_Printf("Could not open %s\n", name);
1855                 return;
1856         }
1857
1858         Con_Printf("Reading %s...\n", name);
1859         VectorClear(leakorg);
1860         c = 0;
1861         s = 0;
1862         pointfilepos = pointfile;
1863         while (*pointfilepos)
1864         {
1865                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1866                         pointfilepos++;
1867                 if (!*pointfilepos)
1868                         break;
1869                 t = pointfilepos;
1870                 while (*t && *t != '\n' && *t != '\r')
1871                         t++;
1872                 tchar = *t;
1873                 *t = 0;
1874 #if _MSC_VER >= 1400
1875 #define sscanf sscanf_s
1876 #endif
1877                 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1878                 VectorCopy(org, vecorg);
1879                 *t = tchar;
1880                 pointfilepos = t;
1881                 if (r != 3)
1882                         break;
1883                 if (c == 0)
1884                         VectorCopy(org, leakorg);
1885                 c++;
1886
1887                 if (cl.num_particles < cl.max_particles - 3)
1888                 {
1889                         s++;
1890                         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);
1891                 }
1892         }
1893         Mem_Free(pointfile);
1894         VectorCopy(leakorg, vecorg);
1895         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1896
1897         if (c == 0)
1898         {
1899                 return;
1900         }
1901
1902         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);
1903         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);
1904         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);
1905 }
1906
1907 /*
1908 ===============
1909 CL_ParseParticleEffect
1910
1911 Parse an effect out of the server message
1912 ===============
1913 */
1914 void CL_ParseParticleEffect (void)
1915 {
1916         vec3_t org, dir;
1917         int i, count, msgcount, color;
1918
1919         MSG_ReadVector(&cl_message, org, cls.protocol);
1920         for (i=0 ; i<3 ; i++)
1921                 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1922         msgcount = MSG_ReadByte(&cl_message);
1923         color = MSG_ReadByte(&cl_message);
1924
1925         if (msgcount == 255)
1926                 count = 1024;
1927         else
1928                 count = msgcount;
1929
1930         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1931 }
1932
1933 /*
1934 ===============
1935 CL_ParticleExplosion
1936
1937 ===============
1938 */
1939 void CL_ParticleExplosion (const vec3_t org)
1940 {
1941         int i;
1942         trace_t trace;
1943
1944         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1945         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1946
1947         if (cl_particles_quake.integer)
1948         {
1949                 for (i = 0; i < 1024; i++)
1950                 {
1951                         int color;
1952                         int r = rand()&3;
1953
1954                         if (i & 1)
1955                         {
1956                                 color = particlepalette[ramp1[r]];
1957
1958                                 CL_NewQuakeParticle(
1959                                         org,
1960                                         pt_explode,
1961                                         color, color,
1962                                         0.05,                        // gravity
1963                                         org[0], org[1], org[2],      // offset
1964                                         0, 0, 0,                     // velocity
1965                                         2,                           // air friction
1966                                         0,                           // liquid friction
1967                                         16,                          // origin jitter
1968                                         256,                         // velocity jitter
1969                                         5                            // lifetime
1970                                 );
1971                         }
1972                         else
1973                         {
1974                                 color = particlepalette[ramp2[r]];
1975
1976                                 CL_NewQuakeParticle(
1977                                         org,
1978                                         pt_explode2,
1979                                         color, color,
1980                                         0.05,                        // gravity
1981                                         org[0], org[1], org[2],      // offset
1982                                         0, 0, 0,                     // velocity
1983                                         0,                           // air friction
1984                                         0,                           // liquid friction
1985                                         16,                          // origin jitter
1986                                         256,                         // velocity jitter
1987                                         5                            // lifetime
1988                                 );
1989                         }
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, pt_alphastatic, 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         // LadyHavoc: early out conditions
2925         if (!cl.num_particles)
2926                 return;
2927
2928         // Handling of the colour ramp for pt_explode and pt_explode2
2929         pt_explode_frame_interval = frametime * 10;
2930         pt_explode2_frame_interval = frametime * 15;
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                                         break;
3103                                 case pt_explode:
3104                                         // Progress the particle colour up the ramp
3105                                         p->time2 += pt_explode_frame_interval;
3106                                         if (p->time2 >= 8)
3107                                                 p->die = -1;
3108                                         else
3109                                         {
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                                 case pt_explode2:
3117                                         // Progress the particle colour up the ramp
3118                                         p->time2 += pt_explode2_frame_interval;
3119                                         if (p->time2 >= 8)
3120                                                 p->die = -1;
3121                                         else
3122                                         {
3123                                                 color = particlepalette[ramp2[(int)p->time2]];
3124                                                 p->color[0] = color >> 16;
3125                                                 p->color[1] = color >>  8;
3126                                                 p->color[2] = color >>  0;
3127                                         }
3128                                         break;
3129                                 default:
3130                                         break;
3131                                 }
3132                         }
3133                 }
3134                 else if (p->delayedspawn > cl.time)
3135                         continue;
3136                 if (!drawparticles)
3137                         continue;
3138                 // don't render particles too close to the view (they chew fillrate)
3139                 // also don't render particles behind the view (useless)
3140                 // further checks to cull to the frustum would be too slow here
3141                 switch(p->typeindex)
3142                 {
3143                 case pt_beam:
3144                         // beams have no culling
3145                         R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3146                         break;
3147                 default:
3148                         if(cl_particles_visculling.integer)
3149                                 if (!r_refdef.viewcache.world_novis)
3150                                         if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3151                                         {
3152                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3153                                                 if(leaf)
3154                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3155                                                                 continue;
3156                                         }
3157                         // anything else just has to be in front of the viewer and visible at this distance
3158                         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)))
3159                                 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3160                         break;
3161                 }
3162
3163                 continue;
3164 killparticle:
3165                 p->typeindex = 0;
3166                 if (cl.free_particle > i)
3167                         cl.free_particle = i;
3168         }
3169
3170         // reduce cl.num_particles if possible
3171         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3172                 cl.num_particles--;
3173
3174         if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3175         {
3176                 particle_t *oldparticles = cl.particles;
3177                 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3178                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3179                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3180                 Mem_Free(oldparticles);
3181         }
3182 }