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