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