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