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