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