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