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