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