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