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