]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_particles.c
fixed bug with effectinfo.txt loading so that it now loads the proper
[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->delayedcollisions = 0;
657         part->qualityreduction = pqualityreduction;
658         part->angle = angle;
659         part->spin = spin;
660         // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
661         if (part->typeindex == pt_rain)
662         {
663                 int i;
664                 particle_t *part2;
665                 float lifetime = part->die - cl.time;
666                 vec3_t endvec;
667                 trace_t trace;
668                 // turn raindrop into simple spark and create delayedspawn splash effect
669                 part->typeindex = pt_spark;
670                 part->bounce = 0;
671                 VectorMA(part->org, lifetime, part->vel, endvec);
672                 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
673                 part->die = cl.time + lifetime * trace.fraction;
674                 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);
675                 if (part2)
676                 {
677                         part2->delayedspawn = part->die;
678                         part2->die += part->die - cl.time;
679                         for (i = rand() & 7;i < 10;i++)
680                         {
681                                 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);
682                                 if (part2)
683                                 {
684                                         part2->delayedspawn = part->die;
685                                         part2->die += part->die - cl.time;
686                                 }
687                         }
688                 }
689         }
690 #if 0
691         else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
692         {
693                 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
694                 vec3_t endvec;
695                 trace_t trace;
696                 VectorMA(part->org, lifetime, part->vel, endvec);
697                 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
698                 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
699         }
700 #endif
701
702         return part;
703 }
704
705 static void CL_ImmediateBloodStain(particle_t *part)
706 {
707         vec3_t v;
708         int staintex;
709
710         // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
711         if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
712         {
713                 VectorCopy(part->vel, v);
714                 VectorNormalize(v);
715                 staintex = part->staintexnum;
716                 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);
717         }
718
719         // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
720         if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
721         {
722                 VectorCopy(part->vel, v);
723                 VectorNormalize(v);
724                 staintex = tex_blooddecal[rand()&7];
725                 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);
726         }
727 }
728
729 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
730 {
731         int l1, l2;
732         decal_t *decal;
733         entity_render_t *ent = &cl.entities[hitent].render;
734         unsigned char color[3];
735         if (!cl_decals.integer)
736                 return;
737         if (!ent->allowdecals)
738                 return;
739
740         l2 = (int)lhrandom(0.5, 256.5);
741         l1 = 256 - l2;
742         color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
743         color[1] = ((((color1 >>  8) & 0xFF) * l1 + ((color2 >>  8) & 0xFF) * l2) >> 8) & 0xFF;
744         color[2] = ((((color1 >>  0) & 0xFF) * l1 + ((color2 >>  0) & 0xFF) * l2) >> 8) & 0xFF;
745
746         if (cl_decals_newsystem.integer)
747         {
748                 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);
749                 return;
750         }
751
752         for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
753         if (cl.free_decal >= cl.max_decals)
754                 return;
755         decal = &cl.decals[cl.free_decal++];
756         if (cl.num_decals < cl.free_decal)
757                 cl.num_decals = cl.free_decal;
758         memset(decal, 0, sizeof(*decal));
759         decal->decalsequence = cl.decalsequence++;
760         decal->typeindex = pt_decal;
761         decal->texnum = texnum;
762         VectorMA(org, cl_decals_bias.value, normal, decal->org);
763         VectorCopy(normal, decal->normal);
764         decal->size = size;
765         decal->alpha = alpha;
766         decal->time2 = cl.time;
767         decal->color[0] = color[0];
768         decal->color[1] = color[1];
769         decal->color[2] = color[2];
770         decal->owner = hitent;
771         decal->clusterindex = -1000; // no vis culling unless we're sure
772         if (hitent)
773         {
774                 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
775                 decal->ownermodel = cl.entities[decal->owner].render.model;
776                 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
777                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
778         }
779         else
780         {
781                 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
782                 {
783                         mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
784                         if(leaf)
785                                 decal->clusterindex = leaf->clusterindex;
786                 }
787         }
788 }
789
790 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
791 {
792         int i;
793         float bestfrac, bestorg[3], bestnormal[3];
794         float org2[3];
795         int besthitent = 0, hitent;
796         trace_t trace;
797         bestfrac = 10;
798         for (i = 0;i < 32;i++)
799         {
800                 VectorRandom(org2);
801                 VectorMA(org, maxdist, org2, org2);
802                 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
803                 // take the closest trace result that doesn't end up hitting a NOMARKS
804                 // surface (sky for example)
805                 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
806                 {
807                         bestfrac = trace.fraction;
808                         besthitent = hitent;
809                         VectorCopy(trace.endpos, bestorg);
810                         VectorCopy(trace.plane.normal, bestnormal);
811                 }
812         }
813         if (bestfrac < 1)
814                 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
815 }
816
817 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
818 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
819 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)
820 {
821         vec3_t center;
822         matrix4x4_t tempmatrix;
823         particle_t *part;
824         VectorLerp(originmins, 0.5, originmaxs, center);
825         Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
826         if (effectnameindex == EFFECT_SVC_PARTICLE)
827         {
828                 if (cl_particles.integer)
829                 {
830                         // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
831                         if (count == 1024)
832                                 CL_ParticleExplosion(center);
833                         else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
834                                 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
835                         else
836                         {
837                                 count *= cl_particles_quality.value;
838                                 for (;count > 0;count--)
839                                 {
840                                         int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
841                                         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);
842                                 }
843                         }
844                 }
845         }
846         else if (effectnameindex == EFFECT_TE_WIZSPIKE)
847                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
848         else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
849                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
850         else if (effectnameindex == EFFECT_TE_SPIKE)
851         {
852                 if (cl_particles_bulletimpacts.integer)
853                 {
854                         if (cl_particles_quake.integer)
855                         {
856                                 if (cl_particles_smoke.integer)
857                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
858                         }
859                         else
860                         {
861                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
862                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
863                                 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);
864                         }
865                 }
866                 // bullet hole
867                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
868                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
869         }
870         else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
871         {
872                 if (cl_particles_bulletimpacts.integer)
873                 {
874                         if (cl_particles_quake.integer)
875                         {
876                                 if (cl_particles_smoke.integer)
877                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
878                         }
879                         else
880                         {
881                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
882                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
883                                 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);
884                         }
885                 }
886                 // bullet hole
887                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
888                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
889                 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);
890         }
891         else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
892         {
893                 if (cl_particles_bulletimpacts.integer)
894                 {
895                         if (cl_particles_quake.integer)
896                         {
897                                 if (cl_particles_smoke.integer)
898                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
899                         }
900                         else
901                         {
902                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
903                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
904                                 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);
905                         }
906                 }
907                 // bullet hole
908                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
909                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
910         }
911         else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
912         {
913                 if (cl_particles_bulletimpacts.integer)
914                 {
915                         if (cl_particles_quake.integer)
916                         {
917                                 if (cl_particles_smoke.integer)
918                                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
919                         }
920                         else
921                         {
922                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
923                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
924                                 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);
925                         }
926                 }
927                 // bullet hole
928                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
929                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
930                 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);
931         }
932         else if (effectnameindex == EFFECT_TE_BLOOD)
933         {
934                 if (!cl_particles_blood.integer)
935                         return;
936                 if (cl_particles_quake.integer)
937                         CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
938                 else
939                 {
940                         static double bloodaccumulator = 0;
941                         qboolean immediatebloodstain = true;
942                         //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);
943                         bloodaccumulator += count * 0.333 * cl_particles_quality.value;
944                         for (;bloodaccumulator > 0;bloodaccumulator--)
945                         {
946                                 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);
947                                 if (immediatebloodstain && part)
948                                 {
949                                         immediatebloodstain = false;
950                                         CL_ImmediateBloodStain(part);
951                                 }
952                         }
953                 }
954         }
955         else if (effectnameindex == EFFECT_TE_SPARK)
956                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
957         else if (effectnameindex == EFFECT_TE_PLASMABURN)
958         {
959                 // plasma scorch mark
960                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
961                 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
962                 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
963         }
964         else if (effectnameindex == EFFECT_TE_GUNSHOT)
965         {
966                 if (cl_particles_bulletimpacts.integer)
967                 {
968                         if (cl_particles_quake.integer)
969                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
970                         else
971                         {
972                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
973                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
974                                 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);
975                         }
976                 }
977                 // bullet hole
978                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
979                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
980         }
981         else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
982         {
983                 if (cl_particles_bulletimpacts.integer)
984                 {
985                         if (cl_particles_quake.integer)
986                                 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
987                         else
988                         {
989                                 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
990                                 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
991                                 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);
992                         }
993                 }
994                 // bullet hole
995                 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
996                 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
997                 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);
998         }
999         else if (effectnameindex == EFFECT_TE_EXPLOSION)
1000         {
1001                 CL_ParticleExplosion(center);
1002                 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);
1003         }
1004         else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1005         {
1006                 CL_ParticleExplosion(center);
1007                 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);
1008         }
1009         else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1010         {
1011                 if (cl_particles_quake.integer)
1012                 {
1013                         int i;
1014                         for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1015                         {
1016                                 if (i & 1)
1017                                         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);
1018                                 else
1019                                         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);
1020                         }
1021                 }
1022                 else
1023                         CL_ParticleExplosion(center);
1024                 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);
1025         }
1026         else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1027                 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);
1028         else if (effectnameindex == EFFECT_TE_FLAMEJET)
1029         {
1030                 count *= cl_particles_quality.value;
1031                 while (count-- > 0)
1032                         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);
1033         }
1034         else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1035         {
1036                 float i, j, inc, vel;
1037                 vec3_t dir, org;
1038
1039                 inc = 8 / cl_particles_quality.value;
1040                 for (i = -128;i < 128;i += inc)
1041                 {
1042                         for (j = -128;j < 128;j += inc)
1043                         {
1044                                 dir[0] = j + lhrandom(0, inc);
1045                                 dir[1] = i + lhrandom(0, inc);
1046                                 dir[2] = 256;
1047                                 org[0] = center[0] + dir[0];
1048                                 org[1] = center[1] + dir[1];
1049                                 org[2] = center[2] + lhrandom(0, 64);
1050                                 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1051                                 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);
1052                         }
1053                 }
1054         }
1055         else if (effectnameindex == EFFECT_TE_TELEPORT)
1056         {
1057                 float i, j, k, inc, vel;
1058                 vec3_t dir;
1059
1060                 if (cl_particles_quake.integer)
1061                         inc = 4 / cl_particles_quality.value;
1062                 else
1063                         inc = 8 / cl_particles_quality.value;
1064                 for (i = -16;i < 16;i += inc)
1065                 {
1066                         for (j = -16;j < 16;j += inc)
1067                         {
1068                                 for (k = -24;k < 32;k += inc)
1069                                 {
1070                                         VectorSet(dir, i*8, j*8, k*8);
1071                                         VectorNormalize(dir);
1072                                         vel = lhrandom(50, 113);
1073                                         if (cl_particles_quake.integer)
1074                                                 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);
1075                                         else
1076                                                 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);
1077                                 }
1078                         }
1079                 }
1080                 if (!cl_particles_quake.integer)
1081                         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);
1082                 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);
1083         }
1084         else if (effectnameindex == EFFECT_TE_TEI_G3)
1085                 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);
1086         else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1087         {
1088                 if (cl_particles_smoke.integer)
1089                 {
1090                         count *= 0.25f * cl_particles_quality.value;
1091                         while (count-- > 0)
1092                                 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);
1093                 }
1094         }
1095         else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1096         {
1097                 CL_ParticleExplosion(center);
1098                 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);
1099         }
1100         else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1101         {
1102                 float f;
1103                 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1104                 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1105                 if (cl_particles_smoke.integer)
1106                         for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1107                                 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);
1108                 if (cl_particles_sparks.integer)
1109                         for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1110                                 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);
1111                 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);
1112         }
1113         else if (effectnameindex == EFFECT_EF_FLAME)
1114         {
1115                 count *= 300 * cl_particles_quality.value;
1116                 while (count-- > 0)
1117                         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);
1118                 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);
1119         }
1120         else if (effectnameindex == EFFECT_EF_STARDUST)
1121         {
1122                 count *= 200 * cl_particles_quality.value;
1123                 while (count-- > 0)
1124                         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);
1125                 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);
1126         }
1127         else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1128         {
1129                 vec3_t dir, pos;
1130                 float len, dec, qd;
1131                 int smoke, blood, bubbles, r, color;
1132
1133                 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1134                 {
1135                         vec4_t light;
1136                         Vector4Set(light, 0, 0, 0, 0);
1137
1138                         if (effectnameindex == EFFECT_TR_ROCKET)
1139                                 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1140                         else if (effectnameindex == EFFECT_TR_VORESPIKE)
1141                         {
1142                                 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1143                                         Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1144                                 else
1145                                         Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1146                         }
1147                         else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1148                                 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1149
1150                         if (light[3])
1151                         {
1152                                 matrix4x4_t tempmatrix;
1153                                 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1154                                 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);
1155                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1156                         }
1157                 }
1158
1159                 if (!spawnparticles)
1160                         return;
1161
1162                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1163                         return;
1164
1165                 VectorSubtract(originmaxs, originmins, dir);
1166                 len = VectorNormalizeLength(dir);
1167                 if (ent)
1168                 {
1169                         dec = -ent->persistent.trail_time;
1170                         ent->persistent.trail_time += len;
1171                         if (ent->persistent.trail_time < 0.01f)
1172                                 return;
1173
1174                         // if we skip out, leave it reset
1175                         ent->persistent.trail_time = 0.0f;
1176                 }
1177                 else
1178                         dec = 0;
1179
1180                 // advance into this frame to reach the first puff location
1181                 VectorMA(originmins, dec, dir, pos);
1182                 len -= dec;
1183
1184                 smoke = cl_particles.integer && cl_particles_smoke.integer;
1185                 blood = cl_particles.integer && cl_particles_blood.integer;
1186                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1187                 qd = 1.0f / cl_particles_quality.value;
1188
1189                 while (len >= 0)
1190                 {
1191                         dec = 3;
1192                         if (blood)
1193                         {
1194                                 if (effectnameindex == EFFECT_TR_BLOOD)
1195                                 {
1196                                         if (cl_particles_quake.integer)
1197                                         {
1198                                                 color = particlepalette[67 + (rand()&3)];
1199                                                 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);
1200                                         }
1201                                         else
1202                                         {
1203                                                 dec = 16;
1204                                                 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);
1205                                         }
1206                                 }
1207                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1208                                 {
1209                                         if (cl_particles_quake.integer)
1210                                         {
1211                                                 dec = 6;
1212                                                 color = particlepalette[67 + (rand()&3)];
1213                                                 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);
1214                                         }
1215                                         else
1216                                         {
1217                                                 dec = 32;
1218                                                 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);
1219                                         }
1220                                 }
1221                         }
1222                         if (smoke)
1223                         {
1224                                 if (effectnameindex == EFFECT_TR_ROCKET)
1225                                 {
1226                                         if (cl_particles_quake.integer)
1227                                         {
1228                                                 r = rand()&3;
1229                                                 color = particlepalette[ramp3[r]];
1230                                                 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);
1231                                         }
1232                                         else
1233                                         {
1234                                                 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);
1235                                                 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);
1236                                         }
1237                                 }
1238                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1239                                 {
1240                                         if (cl_particles_quake.integer)
1241                                         {
1242                                                 r = 2 + (rand()%5);
1243                                                 color = particlepalette[ramp3[r]];
1244                                                 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);
1245                                         }
1246                                         else
1247                                         {
1248                                                 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);
1249                                         }
1250                                 }
1251                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1252                                 {
1253                                         if (cl_particles_quake.integer)
1254                                         {
1255                                                 dec = 6;
1256                                                 color = particlepalette[52 + (rand()&7)];
1257                                                 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);
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                                         }
1260                                         else if (gamemode == GAME_GOODVSBAD2)
1261                                         {
1262                                                 dec = 6;
1263                                                 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);
1264                                         }
1265                                         else
1266                                         {
1267                                                 color = particlepalette[20 + (rand()&7)];
1268                                                 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);
1269                                         }
1270                                 }
1271                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1272                                 {
1273                                         if (cl_particles_quake.integer)
1274                                         {
1275                                                 dec = 6;
1276                                                 color = particlepalette[230 + (rand()&7)];
1277                                                 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);
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                                         }
1280                                         else
1281                                         {
1282                                                 color = particlepalette[226 + (rand()&7)];
1283                                                 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);
1284                                         }
1285                                 }
1286                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1287                                 {
1288                                         if (cl_particles_quake.integer)
1289                                         {
1290                                                 color = particlepalette[152 + (rand()&3)];
1291                                                 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);
1292                                         }
1293                                         else if (gamemode == GAME_GOODVSBAD2)
1294                                         {
1295                                                 dec = 6;
1296                                                 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);
1297                                         }
1298                                         else if (gamemode == GAME_PRYDON)
1299                                         {
1300                                                 dec = 6;
1301                                                 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);
1302                                         }
1303                                         else
1304                                                 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);
1305                                 }
1306                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1307                                 {
1308                                         dec = 7;
1309                                         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);
1310                                 }
1311                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1312                                 {
1313                                         dec = 4;
1314                                         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);
1315                                 }
1316                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1317                                         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);
1318                         }
1319                         if (bubbles)
1320                         {
1321                                 if (effectnameindex == EFFECT_TR_ROCKET)
1322                                         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);
1323                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1324                                         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);
1325                         }
1326                         // advance to next time and position
1327                         dec *= qd;
1328                         len -= dec;
1329                         VectorMA (pos, dec, dir, pos);
1330                 }
1331                 if (ent)
1332                         ent->persistent.trail_time = len;
1333         }
1334         else
1335                 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1336 }
1337
1338 // this is also called on point effects with spawndlight = true and
1339 // spawnparticles = true
1340 // it is called CL_ParticleTrail because most code does not want to supply
1341 // these parameters, only trail handling does
1342 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)
1343 {
1344         qboolean found = false;
1345         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1346         {
1347                 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1348                 return; // no such effect
1349         }
1350         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1351         {
1352                 int effectinfoindex;
1353                 int supercontents;
1354                 int tex, staintex;
1355                 particleeffectinfo_t *info;
1356                 vec3_t center;
1357                 vec3_t traildir;
1358                 vec3_t trailpos;
1359                 vec3_t rvec;
1360                 vec_t traillen;
1361                 vec_t trailstep;
1362                 qboolean underwater;
1363                 qboolean immediatebloodstain;
1364                 particle_t *part;
1365                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1366                 VectorLerp(originmins, 0.5, originmaxs, center);
1367                 supercontents = CL_PointSuperContents(center);
1368                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1369                 VectorSubtract(originmaxs, originmins, traildir);
1370                 traillen = VectorLength(traildir);
1371                 VectorNormalize(traildir);
1372                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1373                 {
1374                         if (info->effectnameindex == effectnameindex)
1375                         {
1376                                 found = true;
1377                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1378                                         continue;
1379                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1380                                         continue;
1381
1382                                 // spawn a dlight if requested
1383                                 if (info->lightradiusstart > 0 && spawndlight)
1384                                 {
1385                                         matrix4x4_t tempmatrix;
1386                                         if (info->trailspacing > 0)
1387                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1388                                         else
1389                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1390                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1391                                         {
1392                                                 // light flash (explosion, etc)
1393                                                 // called when effect starts
1394                                                 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);
1395                                         }
1396                                         else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1397                                         {
1398                                                 // glowing entity
1399                                                 // called by CL_LinkNetworkEntity
1400                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1401                                                 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);
1402                                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1403                                         }
1404                                 }
1405
1406                                 if (!spawnparticles)
1407                                         continue;
1408
1409                                 // spawn particles
1410                                 tex = info->tex[0];
1411                                 if (info->tex[1] > info->tex[0])
1412                                 {
1413                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1414                                         tex = min(tex, info->tex[1] - 1);
1415                                 }
1416                                 if(info->staintex[0] < 0)
1417                                         staintex = info->staintex[0];
1418                                 else
1419                                 {
1420                                         staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1421                                         staintex = min(staintex, info->staintex[1] - 1);
1422                                 }
1423                                 if (info->particletype == pt_decal)
1424                                         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]);
1425                                 else if (info->orientation == PARTICLE_HBEAM)
1426                                         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);
1427                                 else
1428                                 {
1429                                         if (!cl_particles.integer)
1430                                                 continue;
1431                                         switch (info->particletype)
1432                                         {
1433                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1434                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1435                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1436                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1437                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1438                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1439                                         default: break;
1440                                         }
1441                                         VectorCopy(originmins, trailpos);
1442                                         if (info->trailspacing > 0)
1443                                         {
1444                                                 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1445                                                 trailstep = info->trailspacing / cl_particles_quality.value;
1446                                                 immediatebloodstain = false;
1447                                         }
1448                                         else
1449                                         {
1450                                                 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1451                                                 trailstep = 0;
1452                                                 immediatebloodstain = info->particletype == pt_blood || staintex;
1453                                         }
1454                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1455                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1456                                         {
1457                                                 if (info->tex[1] > info->tex[0])
1458                                                 {
1459                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1460                                                         tex = min(tex, info->tex[1] - 1);
1461                                                 }
1462                                                 if (!trailstep)
1463                                                 {
1464                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1465                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1466                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1467                                                 }
1468                                                 VectorRandom(rvec);
1469                                                 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]));
1470                                                 if (immediatebloodstain && part)
1471                                                 {
1472                                                         immediatebloodstain = false;
1473                                                         CL_ImmediateBloodStain(part);
1474                                                 }
1475                                                 if (trailstep)
1476                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1477                                         }
1478                                 }
1479                         }
1480                 }
1481         }
1482         if (!found)
1483                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1484 }
1485
1486 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)
1487 {
1488         CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1489 }
1490
1491 /*
1492 ===============
1493 CL_EntityParticles
1494 ===============
1495 */
1496 void CL_EntityParticles (const entity_t *ent)
1497 {
1498         int i;
1499         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1500         static vec3_t avelocities[NUMVERTEXNORMALS];
1501         if (!cl_particles.integer) return;
1502         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1503
1504         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1505
1506         if (!avelocities[0][0])
1507                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1508                         avelocities[0][i] = lhrandom(0, 2.55);
1509
1510         for (i = 0;i < NUMVERTEXNORMALS;i++)
1511         {
1512                 yaw = cl.time * avelocities[i][0];
1513                 pitch = cl.time * avelocities[i][1];
1514                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1515                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1516                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1517                 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);
1518         }
1519 }
1520
1521
1522 void CL_ReadPointFile_f (void)
1523 {
1524         vec3_t org, leakorg;
1525         int r, c, s;
1526         char *pointfile = NULL, *pointfilepos, *t, tchar;
1527         char name[MAX_QPATH];
1528
1529         if (!cl.worldmodel)
1530                 return;
1531
1532         dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1533         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1534         if (!pointfile)
1535         {
1536                 Con_Printf("Could not open %s\n", name);
1537                 return;
1538         }
1539
1540         Con_Printf("Reading %s...\n", name);
1541         VectorClear(leakorg);
1542         c = 0;
1543         s = 0;
1544         pointfilepos = pointfile;
1545         while (*pointfilepos)
1546         {
1547                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1548                         pointfilepos++;
1549                 if (!*pointfilepos)
1550                         break;
1551                 t = pointfilepos;
1552                 while (*t && *t != '\n' && *t != '\r')
1553                         t++;
1554                 tchar = *t;
1555                 *t = 0;
1556 #if _MSC_VER >= 1400
1557 #define sscanf sscanf_s
1558 #endif
1559                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1560                 *t = tchar;
1561                 pointfilepos = t;
1562                 if (r != 3)
1563                         break;
1564                 if (c == 0)
1565                         VectorCopy(org, leakorg);
1566                 c++;
1567
1568                 if (cl.num_particles < cl.max_particles - 3)
1569                 {
1570                         s++;
1571                         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);
1572                 }
1573         }
1574         Mem_Free(pointfile);
1575         VectorCopy(leakorg, org);
1576         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1577
1578         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);
1579         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);
1580         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);
1581 }
1582
1583 /*
1584 ===============
1585 CL_ParseParticleEffect
1586
1587 Parse an effect out of the server message
1588 ===============
1589 */
1590 void CL_ParseParticleEffect (void)
1591 {
1592         vec3_t org, dir;
1593         int i, count, msgcount, color;
1594
1595         MSG_ReadVector(org, cls.protocol);
1596         for (i=0 ; i<3 ; i++)
1597                 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1598         msgcount = MSG_ReadByte ();
1599         color = MSG_ReadByte ();
1600
1601         if (msgcount == 255)
1602                 count = 1024;
1603         else
1604                 count = msgcount;
1605
1606         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1607 }
1608
1609 /*
1610 ===============
1611 CL_ParticleExplosion
1612
1613 ===============
1614 */
1615 void CL_ParticleExplosion (const vec3_t org)
1616 {
1617         int i;
1618         trace_t trace;
1619         //vec3_t v;
1620         //vec3_t v2;
1621         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1622         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1623
1624         if (cl_particles_quake.integer)
1625         {
1626                 for (i = 0;i < 1024;i++)
1627                 {
1628                         int r, color;
1629                         r = rand()&3;
1630                         if (i & 1)
1631                         {
1632                                 color = particlepalette[ramp1[r]];
1633                                 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);
1634                         }
1635                         else
1636                         {
1637                                 color = particlepalette[ramp2[r]];
1638                                 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);
1639                         }
1640                 }
1641         }
1642         else
1643         {
1644                 i = CL_PointSuperContents(org);
1645                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1646                 {
1647                         if (cl_particles.integer && cl_particles_bubbles.integer)
1648                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1649                                         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);
1650                 }
1651                 else
1652                 {
1653                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1654                         {
1655                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1656                                 {
1657                                         int k;
1658                                         vec3_t v, v2;
1659                                         for (k = 0;k < 16;k++)
1660                                         {
1661                                                 VectorRandom(v2);
1662                                                 VectorMA(org, 128, v2, v);
1663                                                 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1664                                                 if (trace.fraction >= 0.1)
1665                                                         break;
1666                                         }
1667                                         VectorSubtract(trace.endpos, org, v2);
1668                                         VectorScale(v2, 2.0f, v2);
1669                                         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);
1670                                 }
1671                         }
1672                 }
1673         }
1674
1675         if (cl_particles_explosions_shell.integer)
1676                 R_NewExplosion(org);
1677 }
1678
1679 /*
1680 ===============
1681 CL_ParticleExplosion2
1682
1683 ===============
1684 */
1685 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1686 {
1687         int i, k;
1688         if (!cl_particles.integer) return;
1689
1690         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1691         {
1692                 k = particlepalette[colorStart + (i % colorLength)];
1693                 if (cl_particles_quake.integer)
1694                         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);
1695                 else
1696                         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);
1697         }
1698 }
1699
1700 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1701 {
1702         vec3_t center;
1703         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1704         if (cl_particles_sparks.integer)
1705         {
1706                 sparkcount *= cl_particles_quality.value;
1707                 while(sparkcount-- > 0)
1708                         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);
1709         }
1710 }
1711
1712 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1713 {
1714         vec3_t center;
1715         VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1716         if (cl_particles_smoke.integer)
1717         {
1718                 smokecount *= cl_particles_quality.value;
1719                 while(smokecount-- > 0)
1720                         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);
1721         }
1722 }
1723
1724 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)
1725 {
1726         vec3_t center;
1727         int k;
1728         if (!cl_particles.integer) return;
1729         VectorMAM(0.5f, mins, 0.5f, maxs, center);
1730
1731         count = (int)(count * cl_particles_quality.value);
1732         while (count--)
1733         {
1734                 k = particlepalette[colorbase + (rand()&3)];
1735                 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);
1736         }
1737 }
1738
1739 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1740 {
1741         int k;
1742         float minz, maxz, lifetime = 30;
1743         vec3_t org;
1744         if (!cl_particles.integer) return;
1745         if (dir[2] < 0) // falling
1746         {
1747                 minz = maxs[2] + dir[2] * 0.1;
1748                 maxz = maxs[2];
1749                 if (cl.worldmodel)
1750                         lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1751         }
1752         else // rising??
1753         {
1754                 minz = mins[2];
1755                 maxz = maxs[2] + dir[2] * 0.1;
1756                 if (cl.worldmodel)
1757                         lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1758         }
1759
1760         count = (int)(count * cl_particles_quality.value);
1761
1762         switch(type)
1763         {
1764         case 0:
1765                 if (!cl_particles_rain.integer) break;
1766                 count *= 4; // ick, this should be in the mod or maps?
1767
1768                 while(count--)
1769                 {
1770                         k = particlepalette[colorbase + (rand()&3)];
1771                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1772                         if (gamemode == GAME_GOODVSBAD2)
1773                                 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);
1774                         else
1775                                 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);
1776                 }
1777                 break;
1778         case 1:
1779                 if (!cl_particles_snow.integer) break;
1780                 while(count--)
1781                 {
1782                         k = particlepalette[colorbase + (rand()&3)];
1783                         VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1784                         if (gamemode == GAME_GOODVSBAD2)
1785                                 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);
1786                         else
1787                                 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);
1788                 }
1789                 break;
1790         default:
1791                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1792         }
1793 }
1794
1795 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1796 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1797 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1798 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1799
1800 #define PARTICLETEXTURESIZE 64
1801 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1802
1803 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1804 {
1805         float dz, f, dot;
1806         vec3_t normal;
1807         dz = 1 - (dx*dx+dy*dy);
1808         if (dz > 0) // it does hit the sphere
1809         {
1810                 f = 0;
1811                 // back side
1812                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1813                 VectorNormalize(normal);
1814                 dot = DotProduct(normal, light);
1815                 if (dot > 0.5) // interior reflection
1816                         f += ((dot *  2) - 1);
1817                 else if (dot < -0.5) // exterior reflection
1818                         f += ((dot * -2) - 1);
1819                 // front side
1820                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1821                 VectorNormalize(normal);
1822                 dot = DotProduct(normal, light);
1823                 if (dot > 0.5) // interior reflection
1824                         f += ((dot *  2) - 1);
1825                 else if (dot < -0.5) // exterior reflection
1826                         f += ((dot * -2) - 1);
1827                 f *= 128;
1828                 f += 16; // just to give it a haze so you can see the outline
1829                 f = bound(0, f, 255);
1830                 return (unsigned char) f;
1831         }
1832         else
1833                 return 0;
1834 }
1835
1836 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1837 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1838 {
1839         *basex = (texnum % particlefontcols) * particlefontcellwidth;
1840         *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1841         *width = particlefontcellwidth;
1842         *height = particlefontcellheight;
1843 }
1844
1845 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1846 {
1847         int basex, basey, w, h, y;
1848         CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1849         if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1850                 Sys_Error("invalid particle texture size for autogenerating");
1851         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1852                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1853 }
1854
1855 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1856 {
1857         int x, y;
1858         float cx, cy, dx, dy, f, iradius;
1859         unsigned char *d;
1860         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1861         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1862         iradius = 1.0f / radius;
1863         alpha *= (1.0f / 255.0f);
1864         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1865         {
1866                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1867                 {
1868                         dx = (x - cx);
1869                         dy = (y - cy);
1870                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1871                         if (f > 0)
1872                         {
1873                                 if (f > 1)
1874                                         f = 1;
1875                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1876                                 d[0] += (int)(f * (blue  - d[0]));
1877                                 d[1] += (int)(f * (green - d[1]));
1878                                 d[2] += (int)(f * (red   - d[2]));
1879                         }
1880                 }
1881         }
1882 }
1883
1884 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1885 {
1886         int i;
1887         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1888         {
1889                 data[0] = bound(minb, data[0], maxb);
1890                 data[1] = bound(ming, data[1], maxg);
1891                 data[2] = bound(minr, data[2], maxr);
1892         }
1893 }
1894
1895 void particletextureinvert(unsigned char *data)
1896 {
1897         int i;
1898         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1899         {
1900                 data[0] = 255 - data[0];
1901                 data[1] = 255 - data[1];
1902                 data[2] = 255 - data[2];
1903         }
1904 }
1905
1906 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1907 static void R_InitBloodTextures (unsigned char *particletexturedata)
1908 {
1909         int i, j, k, m;
1910         size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1911         unsigned char *data = Mem_Alloc(tempmempool, datasize);
1912
1913         // blood particles
1914         for (i = 0;i < 8;i++)
1915         {
1916                 memset(data, 255, datasize);
1917                 for (k = 0;k < 24;k++)
1918                         particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1919                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1920                 particletextureinvert(data);
1921                 setuptex(tex_bloodparticle[i], data, particletexturedata);
1922         }
1923
1924         // blood decals
1925         for (i = 0;i < 8;i++)
1926         {
1927                 memset(data, 255, datasize);
1928                 m = 8;
1929                 for (j = 1;j < 10;j++)
1930                         for (k = min(j, m - 1);k < m;k++)
1931                                 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1932                 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1933                 particletextureinvert(data);
1934                 setuptex(tex_blooddecal[i], data, particletexturedata);
1935         }
1936
1937         Mem_Free(data);
1938 }
1939
1940 //uncomment this to make engine save out particle font to a tga file when run
1941 //#define DUMPPARTICLEFONT
1942
1943 static void R_InitParticleTexture (void)
1944 {
1945         int x, y, d, i, k, m;
1946         int basex, basey, w, h;
1947         float dx, dy, f, s1, t1, s2, t2;
1948         vec3_t light;
1949         char *buf;
1950         fs_offset_t filesize;
1951         char texturename[MAX_QPATH];
1952
1953         // a note: decals need to modulate (multiply) the background color to
1954         // properly darken it (stain), and they need to be able to alpha fade,
1955         // this is a very difficult challenge because it means fading to white
1956         // (no change to background) rather than black (darkening everything
1957         // behind the whole decal polygon), and to accomplish this the texture is
1958         // inverted (dark red blood on white background becomes brilliant cyan
1959         // and white on black background) so we can alpha fade it to black, then
1960         // we invert it again during the blendfunc to make it work...
1961
1962 #ifndef DUMPPARTICLEFONT
1963         decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1964         if (decalskinframe)
1965         {
1966                 particlefonttexture = decalskinframe->base;
1967                 // TODO maybe allow custom grid size?
1968                 particlefontwidth = image_width;
1969                 particlefontheight = image_height;
1970                 particlefontcellwidth = image_width / 8;
1971                 particlefontcellheight = image_height / 8;
1972                 particlefontcols = 8;
1973                 particlefontrows = 8;
1974         }
1975         else
1976 #endif
1977         {
1978                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1979                 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1980                 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1981                 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1982                 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1983
1984                 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1985                 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1986                 particlefontcols = 8;
1987                 particlefontrows = 8;
1988
1989                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1990
1991                 // smoke
1992                 for (i = 0;i < 8;i++)
1993                 {
1994                         memset(data, 255, datasize);
1995                         do
1996                         {
1997                                 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1998                                 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1999                                 m = 0;
2000                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2001                                 {
2002                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2003                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2004                                         {
2005                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2006                                                 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2007                                                 if (d > 0)
2008                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
2009                                                 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2010                                                 d = bound(0, d, 255);
2011                                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2012                                                 if (m < d)
2013                                                         m = d;
2014                                         }
2015                                 }
2016                         }
2017                         while (m < 224);
2018                         setuptex(tex_smoke[i], data, particletexturedata);
2019                 }
2020
2021                 // rain splash
2022                 memset(data, 255, datasize);
2023                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2024                 {
2025                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2027                         {
2028                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2029                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2030                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2031                         }
2032                 }
2033                 setuptex(tex_rainsplash, data, particletexturedata);
2034
2035                 // normal particle
2036                 memset(data, 255, datasize);
2037                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2038                 {
2039                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2040                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2041                         {
2042                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2043                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2044                                 d = bound(0, d, 255);
2045                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2046                         }
2047                 }
2048                 setuptex(tex_particle, data, particletexturedata);
2049
2050                 // rain
2051                 memset(data, 255, datasize);
2052                 light[0] = 1;light[1] = 1;light[2] = 1;
2053                 VectorNormalize(light);
2054                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2055                 {
2056                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2057                         // stretch upper half of bubble by +50% and shrink lower half by -50%
2058                         // (this gives an elongated teardrop shape)
2059                         if (dy > 0.5f)
2060                                 dy = (dy - 0.5f) * 2.0f;
2061                         else
2062                                 dy = (dy - 0.5f) / 1.5f;
2063                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2064                         {
2065                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2066                                 // shrink bubble width to half
2067                                 dx *= 2.0f;
2068                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2069                         }
2070                 }
2071                 setuptex(tex_raindrop, data, particletexturedata);
2072
2073                 // bubble
2074                 memset(data, 255, datasize);
2075                 light[0] = 1;light[1] = 1;light[2] = 1;
2076                 VectorNormalize(light);
2077                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2078                 {
2079                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2080                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
2081                         {
2082                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2083                                 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2084                         }
2085                 }
2086                 setuptex(tex_bubble, data, particletexturedata);
2087
2088                 // Blood particles and blood decals
2089                 R_InitBloodTextures (particletexturedata);
2090
2091                 // bullet decals
2092                 for (i = 0;i < 8;i++)
2093                 {
2094                         memset(data, 255, datasize);
2095                         for (k = 0;k < 12;k++)
2096                                 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2097                         for (k = 0;k < 3;k++)
2098                                 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2099                         //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2100                         particletextureinvert(data);
2101                         setuptex(tex_bulletdecal[i], data, particletexturedata);
2102                 }
2103
2104 #ifdef DUMPPARTICLEFONT
2105                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2106 #endif
2107
2108                 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2109                 particlefonttexture = decalskinframe->base;
2110
2111                 Mem_Free(particletexturedata);
2112                 Mem_Free(data);
2113                 Mem_Free(noise1);
2114                 Mem_Free(noise2);
2115         }
2116         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2117         {
2118                 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2119                 particletexture[i].texture = particlefonttexture;
2120                 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2121                 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2122                 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2123                 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2124         }
2125
2126 #ifndef DUMPPARTICLEFONT
2127         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2128         if (!particletexture[tex_beam].texture)
2129 #endif
2130         {
2131                 unsigned char noise3[64][64], data2[64][16][4];
2132                 // nexbeam
2133                 fractalnoise(&noise3[0][0], 64, 4);
2134                 m = 0;
2135                 for (y = 0;y < 64;y++)
2136                 {
2137                         dy = (y - 0.5f*64) / (64*0.5f-1);
2138                         for (x = 0;x < 16;x++)
2139                         {
2140                                 dx = (x - 0.5f*16) / (16*0.5f-2);
2141                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2142                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2143                                 data2[y][x][3] = 255;
2144                         }
2145                 }
2146
2147 #ifdef DUMPPARTICLEFONT
2148                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2149 #endif
2150                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2151         }
2152         particletexture[tex_beam].s1 = 0;
2153         particletexture[tex_beam].t1 = 0;
2154         particletexture[tex_beam].s2 = 1;
2155         particletexture[tex_beam].t2 = 1;
2156
2157         // now load an texcoord/texture override file
2158         buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2159         if(buf)
2160         {
2161                 const char *bufptr;
2162                 bufptr = buf;
2163                 for(;;)
2164                 {
2165                         if(!COM_ParseToken_Simple(&bufptr, true, false))
2166                                 break;
2167                         if(!strcmp(com_token, "\n"))
2168                                 continue; // empty line
2169                         i = atoi(com_token);
2170
2171                         texturename[0] = 0;
2172                         s1 = 0;
2173                         t1 = 0;
2174                         s2 = 1;
2175                         t2 = 1;
2176
2177                         if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2178                         {
2179                                 s1 = atof(com_token);
2180                                 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2181                                 {
2182                                         t1 = atof(com_token);
2183                                         if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2184                                         {
2185                                                 s2 = atof(com_token);
2186                                                 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2187                                                 {
2188                                                         t2 = atof(com_token);
2189                                                         strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2190                                                         if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2191                                                                 strlcpy(texturename, com_token, sizeof(texturename));
2192                                                 }
2193                                         }
2194                                 }
2195                                 else
2196                                 {
2197                                         s1 = 0;
2198                                         strlcpy(texturename, com_token, sizeof(texturename));
2199                                 }
2200                         }
2201                         if (!texturename[0])
2202                         {
2203                                 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2204                                 continue;
2205                         }
2206                         if (i < 0 || i >= MAX_PARTICLETEXTURES)
2207                         {
2208                                 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2209                                 continue;
2210                         }
2211                         particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2212                         particletexture[i].s1 = s1;
2213                         particletexture[i].t1 = t1;
2214                         particletexture[i].s2 = s2;
2215                         particletexture[i].t2 = t2;
2216                 }
2217                 Mem_Free(buf);
2218         }
2219 }
2220
2221 static void r_part_start(void)
2222 {
2223         int i;
2224         // generate particlepalette for convenience from the main one
2225         for (i = 0;i < 256;i++)
2226                 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2227         particletexturepool = R_AllocTexturePool();
2228         R_InitParticleTexture ();
2229         CL_Particles_LoadEffectInfo();
2230 }
2231
2232 static void r_part_shutdown(void)
2233 {
2234         R_FreeTexturePool(&particletexturepool);
2235 }
2236
2237 static void r_part_newmap(void)
2238 {
2239         if (decalskinframe)
2240                 R_SkinFrame_MarkUsed(decalskinframe);
2241         CL_Particles_LoadEffectInfo();
2242 }
2243
2244 #define BATCHSIZE 256
2245 unsigned short particle_elements[BATCHSIZE*6];
2246 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2247
2248 void R_Particles_Init (void)
2249 {
2250         int i;
2251         for (i = 0;i < BATCHSIZE;i++)
2252         {
2253                 particle_elements[i*6+0] = i*4+0;
2254                 particle_elements[i*6+1] = i*4+1;
2255                 particle_elements[i*6+2] = i*4+2;
2256                 particle_elements[i*6+3] = i*4+0;
2257                 particle_elements[i*6+4] = i*4+2;
2258                 particle_elements[i*6+5] = i*4+3;
2259         }
2260
2261         Cvar_RegisterVariable(&r_drawparticles);
2262         Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2263         Cvar_RegisterVariable(&r_drawdecals);
2264         Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2265         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2266 }
2267
2268 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2269 {
2270         int surfacelistindex;
2271         const decal_t *d;
2272         float *v3f, *t2f, *c4f;
2273         particletexture_t *tex;
2274         float right[3], up[3], size, ca;
2275         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2276         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2277
2278         RSurf_ActiveWorldEntity();
2279
2280         r_refdef.stats.drawndecals += numsurfaces;
2281         R_Mesh_ResetTextureState();
2282         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2283         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2284         R_Mesh_ColorPointer(particle_color4f, 0, 0);
2285         GL_DepthMask(false);
2286         GL_DepthRange(0, 1);
2287         GL_PolygonOffset(0, 0);
2288         GL_DepthTest(true);
2289         GL_CullFace(GL_NONE);
2290
2291         // generate all the vertices at once
2292         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2293         {
2294                 d = cl.decals + surfacelist[surfacelistindex];
2295
2296                 // calculate color
2297                 c4f = particle_color4f + 16*surfacelistindex;
2298                 ca = d->alpha * alphascale;
2299                 // ensure alpha multiplier saturates properly
2300                 if (ca > 1.0f / 256.0f)
2301                         ca = 1.0f / 256.0f;     
2302                 if (r_refdef.fogenabled)
2303                         ca *= RSurf_FogVertex(d->org);
2304                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2305                 Vector4Copy(c4f, c4f + 4);
2306                 Vector4Copy(c4f, c4f + 8);
2307                 Vector4Copy(c4f, c4f + 12);
2308
2309                 // calculate vertex positions
2310                 size = d->size * cl_particles_size.value;
2311                 VectorVectors(d->normal, right, up);
2312                 VectorScale(right, size, right);
2313                 VectorScale(up, size, up);
2314                 v3f = particle_vertex3f + 12*surfacelistindex;
2315                 v3f[ 0] = d->org[0] - right[0] - up[0];
2316                 v3f[ 1] = d->org[1] - right[1] - up[1];
2317                 v3f[ 2] = d->org[2] - right[2] - up[2];
2318                 v3f[ 3] = d->org[0] - right[0] + up[0];
2319                 v3f[ 4] = d->org[1] - right[1] + up[1];
2320                 v3f[ 5] = d->org[2] - right[2] + up[2];
2321                 v3f[ 6] = d->org[0] + right[0] + up[0];
2322                 v3f[ 7] = d->org[1] + right[1] + up[1];
2323                 v3f[ 8] = d->org[2] + right[2] + up[2];
2324                 v3f[ 9] = d->org[0] + right[0] - up[0];
2325                 v3f[10] = d->org[1] + right[1] - up[1];
2326                 v3f[11] = d->org[2] + right[2] - up[2];
2327
2328                 // calculate texcoords
2329                 tex = &particletexture[d->texnum];
2330                 t2f = particle_texcoord2f + 8*surfacelistindex;
2331                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2332                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2333                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2334                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2335         }
2336
2337         // now render the decals all at once
2338         // (this assumes they all use one particle font texture!)
2339         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2340         R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2341         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2342 }
2343
2344 void R_DrawDecals (void)
2345 {
2346         int i;
2347         int drawdecals = r_drawdecals.integer;
2348         decal_t *decal;
2349         float frametime;
2350         float decalfade;
2351         float drawdist2;
2352         int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2353
2354         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2355         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2356
2357         // LordHavoc: early out conditions
2358         if (!cl.num_decals)
2359                 return;
2360
2361         decalfade = frametime * 256 / cl_decals_fadetime.value;
2362         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2363         drawdist2 = drawdist2*drawdist2;
2364
2365         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2366         {
2367                 if (!decal->typeindex)
2368                         continue;
2369
2370                 if (killsequence - decal->decalsequence > 0)
2371                         goto killdecal;
2372
2373                 if (cl.time > decal->time2 + cl_decals_time.value)
2374                 {
2375                         decal->alpha -= decalfade;
2376                         if (decal->alpha <= 0)
2377                                 goto killdecal;
2378                 }
2379
2380                 if (decal->owner)
2381                 {
2382                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2383                         {
2384                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2385                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2386                         }
2387                         else
2388                                 goto killdecal;
2389                 }
2390
2391                 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2392                         continue;
2393
2394                 if (!drawdecals)
2395                         continue;
2396
2397                 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))
2398                         R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2399                 continue;
2400 killdecal:
2401                 decal->typeindex = 0;
2402                 if (cl.free_decal > i)
2403                         cl.free_decal = i;
2404         }
2405
2406         // reduce cl.num_decals if possible
2407         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2408                 cl.num_decals--;
2409
2410         if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2411         {
2412                 decal_t *olddecals = cl.decals;
2413                 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2414                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2415                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2416                 Mem_Free(olddecals);
2417         }
2418
2419         r_refdef.stats.totaldecals = cl.num_decals;
2420 }
2421
2422 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2423 {
2424         int surfacelistindex;
2425         int batchstart, batchcount;
2426         const particle_t *p;
2427         pblend_t blendmode;
2428         rtexture_t *texture;
2429         float *v3f, *t2f, *c4f;
2430         particletexture_t *tex;
2431         float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2432         float ambient[3], diffuse[3], diffusenormal[3];
2433         float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2434         vec4_t colormultiplier;
2435
2436         RSurf_ActiveWorldEntity();
2437
2438         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));
2439
2440         r_refdef.stats.particles += numsurfaces;
2441         R_Mesh_ResetTextureState();
2442         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2443         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2444         R_Mesh_ColorPointer(particle_color4f, 0, 0);
2445         GL_DepthMask(false);
2446         GL_DepthRange(0, 1);
2447         GL_PolygonOffset(0, 0);
2448         GL_DepthTest(true);
2449         GL_AlphaTest(false);
2450         GL_CullFace(GL_NONE);
2451
2452         spintime = r_refdef.scene.time;
2453
2454         // first generate all the vertices at once
2455         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2456         {
2457                 p = cl.particles + surfacelist[surfacelistindex];
2458
2459                 blendmode = (pblend_t)p->blendmode;
2460
2461                 switch (blendmode)
2462                 {
2463                 case PBLEND_INVALID:
2464                 case PBLEND_INVMOD:
2465                         alpha = p->alpha * colormultiplier[3];
2466                         // ensure alpha multiplier saturates properly
2467                         if (alpha > 1.0f)
2468                                 alpha = 1.0f;
2469                         // additive and modulate can just fade out in fog (this is correct)
2470                         if (r_refdef.fogenabled)
2471                                 alpha *= RSurf_FogVertex(p->org);
2472                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2473                         alpha *= 1.0f / 256.0f;
2474                         c4f[0] = p->color[0] * alpha;
2475                         c4f[1] = p->color[1] * alpha;
2476                         c4f[2] = p->color[2] * alpha;
2477                         c4f[3] = 1;
2478                         break;
2479                 case PBLEND_ADD:
2480                         alpha = p->alpha * colormultiplier[3];
2481                         // ensure alpha multiplier saturates properly
2482                         if (alpha > 1.0f)
2483                                 alpha = 1.0f;
2484                         // additive and modulate can just fade out in fog (this is correct)
2485                         if (r_refdef.fogenabled)
2486                                 alpha *= RSurf_FogVertex(p->org);
2487                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2488                         c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2489                         c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2490                         c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2491                         c4f[3] = 1;
2492                         break;
2493                 case PBLEND_ALPHA:
2494                         c4f[0] = p->color[0] * colormultiplier[0];
2495                         c4f[1] = p->color[1] * colormultiplier[1];
2496                         c4f[2] = p->color[2] * colormultiplier[2];
2497                         c4f[3] = p->alpha * colormultiplier[3];
2498                         // note: lighting is not cheap!
2499                         if (particletype[p->typeindex].lighting)
2500                         {
2501                                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2502                                 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2503                                 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2504                                 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2505                         }
2506                         // mix in the fog color
2507                         if (r_refdef.fogenabled)
2508                         {
2509                                 fog = RSurf_FogVertex(p->org);
2510                                 ifog = 1 - fog;
2511                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2512                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2513                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2514                         }
2515                         break;
2516                 }
2517                 // copy the color into the other three vertices
2518                 Vector4Copy(c4f, c4f + 4);
2519                 Vector4Copy(c4f, c4f + 8);
2520                 Vector4Copy(c4f, c4f + 12);
2521
2522                 size = p->size * cl_particles_size.value;
2523                 tex = &particletexture[p->texnum];
2524                 switch(p->orientation)
2525                 {
2526 //              case PARTICLE_INVALID:
2527                 case PARTICLE_BILLBOARD:
2528                         if (p->angle + p->spin)
2529                         {
2530                                 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2531                                 spinsin = sin(spinrad) * size;
2532                                 spincos = cos(spinrad) * size;
2533                                 spinm1 = -p->stretch * spincos;
2534                                 spinm2 = -spinsin;
2535                                 spinm3 = spinsin;
2536                                 spinm4 = -p->stretch * spincos;
2537                                 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2538                                 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2539                         }
2540                         else
2541                         {
2542                                 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2543                                 VectorScale(r_refdef.view.up, size, up);
2544                         }
2545
2546                         v3f[ 0] = p->org[0] - right[0] - up[0];
2547                         v3f[ 1] = p->org[1] - right[1] - up[1];
2548                         v3f[ 2] = p->org[2] - right[2] - up[2];
2549                         v3f[ 3] = p->org[0] - right[0] + up[0];
2550                         v3f[ 4] = p->org[1] - right[1] + up[1];
2551                         v3f[ 5] = p->org[2] - right[2] + up[2];
2552                         v3f[ 6] = p->org[0] + right[0] + up[0];
2553                         v3f[ 7] = p->org[1] + right[1] + up[1];
2554                         v3f[ 8] = p->org[2] + right[2] + up[2];
2555                         v3f[ 9] = p->org[0] + right[0] - up[0];
2556                         v3f[10] = p->org[1] + right[1] - up[1];
2557                         v3f[11] = p->org[2] + right[2] - up[2];
2558                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2559                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2560                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2561                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2562                         break;
2563                 case PARTICLE_ORIENTED_DOUBLESIDED:
2564                         VectorVectors(p->vel, baseright, baseup);
2565                         if (p->angle + p->spin)
2566                         {
2567                                 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2568                                 spinsin = sin(spinrad) * size;
2569                                 spincos = cos(spinrad) * size;
2570                                 spinm1 = p->stretch * spincos;
2571                                 spinm2 = -spinsin;
2572                                 spinm3 = spinsin;
2573                                 spinm4 = p->stretch * spincos;
2574                                 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2575                                 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2576                         }
2577                         else
2578                         {
2579                                 VectorScale(baseright, size * p->stretch, right);
2580                                 VectorScale(baseup, size, up);
2581                         }
2582                         v3f[ 0] = p->org[0] - right[0] - up[0];
2583                         v3f[ 1] = p->org[1] - right[1] - up[1];
2584                         v3f[ 2] = p->org[2] - right[2] - up[2];
2585                         v3f[ 3] = p->org[0] - right[0] + up[0];
2586                         v3f[ 4] = p->org[1] - right[1] + up[1];
2587                         v3f[ 5] = p->org[2] - right[2] + up[2];
2588                         v3f[ 6] = p->org[0] + right[0] + up[0];
2589                         v3f[ 7] = p->org[1] + right[1] + up[1];
2590                         v3f[ 8] = p->org[2] + right[2] + up[2];
2591                         v3f[ 9] = p->org[0] + right[0] - up[0];
2592                         v3f[10] = p->org[1] + right[1] - up[1];
2593                         v3f[11] = p->org[2] + right[2] - up[2];
2594                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2595                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2596                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2597                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2598                         break;
2599                 case PARTICLE_SPARK:
2600                         len = VectorLength(p->vel);
2601                         VectorNormalize2(p->vel, up);
2602                         lenfactor = p->stretch * 0.04 * len;
2603                         if(lenfactor < size * 0.5)
2604                                 lenfactor = size * 0.5;
2605                         VectorMA(p->org, -lenfactor, up, v);
2606                         VectorMA(p->org,  lenfactor, up, up2);
2607                         R_CalcBeam_Vertex3f(v3f, v, up2, size);
2608                         t2f[0] = tex->s1;t2f[1] = tex->t2;
2609                         t2f[2] = tex->s1;t2f[3] = tex->t1;
2610                         t2f[4] = tex->s2;t2f[5] = tex->t1;
2611                         t2f[6] = tex->s2;t2f[7] = tex->t2;
2612                         break;
2613                 case PARTICLE_VBEAM:
2614                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2615                         VectorSubtract(p->vel, p->org, up);
2616                         VectorNormalize(up);
2617                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2618                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2619                         t2f[0] = tex->s2;t2f[1] = v[0];
2620                         t2f[2] = tex->s1;t2f[3] = v[0];
2621                         t2f[4] = tex->s1;t2f[5] = v[1];
2622                         t2f[6] = tex->s2;t2f[7] = v[1];
2623                         break;
2624                 case PARTICLE_HBEAM:
2625                         R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2626                         VectorSubtract(p->vel, p->org, up);
2627                         VectorNormalize(up);
2628                         v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2629                         v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2630                         t2f[0] = v[0];t2f[1] = tex->t1;
2631                         t2f[2] = v[0];t2f[3] = tex->t2;
2632                         t2f[4] = v[1];t2f[5] = tex->t2;
2633                         t2f[6] = v[1];t2f[7] = tex->t1;
2634                         break;
2635                 }
2636         }
2637
2638         // now render batches of particles based on blendmode and texture
2639         blendmode = PBLEND_INVALID;
2640         texture = NULL;
2641         batchstart = 0;
2642         batchcount = 0;
2643         for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2644         {
2645                 p = cl.particles + surfacelist[surfacelistindex];
2646
2647                 if (blendmode != p->blendmode)
2648                 {
2649                         blendmode = (pblend_t)p->blendmode;
2650                         switch(blendmode)
2651                         {
2652                         case PBLEND_ALPHA:
2653                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2654                                 break;
2655                         case PBLEND_INVALID:
2656                         case PBLEND_ADD:
2657                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2658                                 break;
2659                         case PBLEND_INVMOD:
2660                                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2661                                 break;
2662                         }
2663                 }
2664                 if (texture != particletexture[p->texnum].texture)
2665                 {
2666                         texture = particletexture[p->texnum].texture;
2667                         R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2668                 }
2669
2670                 // iterate until we find a change in settings
2671                 batchstart = surfacelistindex++;
2672                 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2673                 {
2674                         p = cl.particles + surfacelist[surfacelistindex];
2675                         if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2676                                 break;
2677                 }
2678
2679                 batchcount = surfacelistindex - batchstart;
2680                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2681         }
2682 }
2683
2684 void R_DrawParticles (void)
2685 {
2686         int i, a;
2687         int drawparticles = r_drawparticles.integer;
2688         float minparticledist;
2689         particle_t *p;
2690         float gravity, frametime, f, dist, oldorg[3];
2691         float drawdist2;
2692         int hitent;
2693         trace_t trace;
2694         qboolean update;
2695
2696         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2697         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2698
2699         // LordHavoc: early out conditions
2700         if (!cl.num_particles)
2701                 return;
2702
2703         minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2704         gravity = frametime * cl.movevars_gravity;
2705         update = frametime > 0;
2706         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2707         drawdist2 = drawdist2*drawdist2;
2708
2709         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2710         {
2711                 if (!p->typeindex)
2712                 {
2713                         if (cl.free_particle > i)
2714                                 cl.free_particle = i;
2715                         continue;
2716                 }
2717
2718                 if (update)
2719                 {
2720                         if (p->delayedspawn > cl.time)
2721                                 continue;
2722                         p->delayedspawn = 0;
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)
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 }