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