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