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