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