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