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