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