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