]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_particles.c
improve keepalive handling (also send keepalives during map loading); but still not...
[divverent/darkplaces.git] / cl_particles.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 #include "cl_collision.h"
24 #include "image.h"
25 #include "r_shadow.h"
26
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
29
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
32 {
33         {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34         {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35         {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36         {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37         {PBLEND_ADD, PARTICLE_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_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);
973                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
974                         }
975                 }
976
977                 if (!spawnparticles)
978                         return;
979
980                 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
981                         return;
982
983                 VectorSubtract(originmaxs, originmins, dir);
984                 len = VectorNormalizeLength(dir);
985                 if (ent)
986                 {
987                         dec = -ent->persistent.trail_time;
988                         ent->persistent.trail_time += len;
989                         if (ent->persistent.trail_time < 0.01f)
990                                 return;
991
992                         // if we skip out, leave it reset
993                         ent->persistent.trail_time = 0.0f;
994                 }
995                 else
996                         dec = 0;
997
998                 // advance into this frame to reach the first puff location
999                 VectorMA(originmins, dec, dir, pos);
1000                 len -= dec;
1001
1002                 smoke = cl_particles.integer && cl_particles_smoke.integer;
1003                 blood = cl_particles.integer && cl_particles_blood.integer;
1004                 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1005                 qd = 1.0f / cl_particles_quality.value;
1006
1007                 while (len >= 0)
1008                 {
1009                         dec = 3;
1010                         if (blood)
1011                         {
1012                                 if (effectnameindex == EFFECT_TR_BLOOD)
1013                                 {
1014                                         if (cl_particles_quake.integer)
1015                                         {
1016                                                 color = particlepalette[67 + (rand()&3)];
1017                                                 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);
1018                                         }
1019                                         else
1020                                         {
1021                                                 dec = 16;
1022                                                 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);
1023                                         }
1024                                 }
1025                                 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1026                                 {
1027                                         if (cl_particles_quake.integer)
1028                                         {
1029                                                 dec = 6;
1030                                                 color = particlepalette[67 + (rand()&3)];
1031                                                 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);
1032                                         }
1033                                         else
1034                                         {
1035                                                 dec = 32;
1036                                                 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);
1037                                         }
1038                                 }
1039                         }
1040                         if (smoke)
1041                         {
1042                                 if (effectnameindex == EFFECT_TR_ROCKET)
1043                                 {
1044                                         if (cl_particles_quake.integer)
1045                                         {
1046                                                 r = rand()&3;
1047                                                 color = particlepalette[ramp3[r]];
1048                                                 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);
1049                                         }
1050                                         else
1051                                         {
1052                                                 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);
1053                                                 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);
1054                                         }
1055                                 }
1056                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1057                                 {
1058                                         if (cl_particles_quake.integer)
1059                                         {
1060                                                 r = 2 + (rand()%5);
1061                                                 color = particlepalette[ramp3[r]];
1062                                                 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);
1063                                         }
1064                                         else
1065                                         {
1066                                                 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);
1067                                         }
1068                                 }
1069                                 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1070                                 {
1071                                         if (cl_particles_quake.integer)
1072                                         {
1073                                                 dec = 6;
1074                                                 color = particlepalette[52 + (rand()&7)];
1075                                                 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);
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                                         }
1078                                         else if (gamemode == GAME_GOODVSBAD2)
1079                                         {
1080                                                 dec = 6;
1081                                                 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);
1082                                         }
1083                                         else
1084                                         {
1085                                                 color = particlepalette[20 + (rand()&7)];
1086                                                 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);
1087                                         }
1088                                 }
1089                                 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1090                                 {
1091                                         if (cl_particles_quake.integer)
1092                                         {
1093                                                 dec = 6;
1094                                                 color = particlepalette[230 + (rand()&7)];
1095                                                 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);
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                                         }
1098                                         else
1099                                         {
1100                                                 color = particlepalette[226 + (rand()&7)];
1101                                                 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);
1102                                         }
1103                                 }
1104                                 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1105                                 {
1106                                         if (cl_particles_quake.integer)
1107                                         {
1108                                                 color = particlepalette[152 + (rand()&3)];
1109                                                 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);
1110                                         }
1111                                         else if (gamemode == GAME_GOODVSBAD2)
1112                                         {
1113                                                 dec = 6;
1114                                                 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);
1115                                         }
1116                                         else if (gamemode == GAME_PRYDON)
1117                                         {
1118                                                 dec = 6;
1119                                                 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);
1120                                         }
1121                                         else
1122                                                 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);
1123                                 }
1124                                 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1125                                 {
1126                                         dec = 7;
1127                                         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);
1128                                 }
1129                                 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1130                                 {
1131                                         dec = 4;
1132                                         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);
1133                                 }
1134                                 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1135                                         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);
1136                         }
1137                         if (bubbles)
1138                         {
1139                                 if (effectnameindex == EFFECT_TR_ROCKET)
1140                                         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);
1141                                 else if (effectnameindex == EFFECT_TR_GRENADE)
1142                                         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);
1143                         }
1144                         // advance to next time and position
1145                         dec *= qd;
1146                         len -= dec;
1147                         VectorMA (pos, dec, dir, pos);
1148                 }
1149                 if (ent)
1150                         ent->persistent.trail_time = len;
1151         }
1152         else if (developer.integer >= 1)
1153                 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1154 }
1155
1156 // this is also called on point effects with spawndlight = true and
1157 // spawnparticles = true
1158 // it is called CL_ParticleTrail because most code does not want to supply
1159 // these parameters, only trail handling does
1160 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)
1161 {
1162         vec3_t center;
1163         qboolean found = false;
1164         if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1165         {
1166                 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1167                 return; // no such effect
1168         }
1169         VectorLerp(originmins, 0.5, originmaxs, center);
1170         if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1171         {
1172                 int effectinfoindex;
1173                 int supercontents;
1174                 int tex;
1175                 particleeffectinfo_t *info;
1176                 vec3_t center;
1177                 vec3_t centervelocity;
1178                 vec3_t traildir;
1179                 vec3_t trailpos;
1180                 vec3_t rvec;
1181                 vec_t traillen;
1182                 vec_t trailstep;
1183                 qboolean underwater;
1184                 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1185                 VectorLerp(originmins, 0.5, originmaxs, center);
1186                 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1187                 supercontents = CL_PointSuperContents(center);
1188                 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1189                 VectorSubtract(originmaxs, originmins, traildir);
1190                 traillen = VectorLength(traildir);
1191                 VectorNormalize(traildir);
1192                 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1193                 {
1194                         if (info->effectnameindex == effectnameindex)
1195                         {
1196                                 found = true;
1197                                 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1198                                         continue;
1199                                 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1200                                         continue;
1201
1202                                 // spawn a dlight if requested
1203                                 if (info->lightradiusstart > 0 && spawndlight)
1204                                 {
1205                                         matrix4x4_t tempmatrix;
1206                                         if (info->trailspacing > 0)
1207                                                 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1208                                         else
1209                                                 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1210                                         if (info->lighttime > 0 && info->lightradiusfade > 0)
1211                                         {
1212                                                 // light flash (explosion, etc)
1213                                                 // called when effect starts
1214                                                 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);
1215                                         }
1216                                         else
1217                                         {
1218                                                 // glowing entity
1219                                                 // called by CL_LinkNetworkEntity
1220                                                 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1221                                                 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);
1222                                                 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1223                                         }
1224                                 }
1225
1226                                 if (!spawnparticles)
1227                                         continue;
1228
1229                                 // spawn particles
1230                                 tex = info->tex[0];
1231                                 if (info->tex[1] > info->tex[0])
1232                                 {
1233                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1234                                         tex = min(tex, info->tex[1] - 1);
1235                                 }
1236                                 if (info->particletype == pt_decal)
1237                                         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]);
1238                                 else if (info->orientation == PARTICLE_BEAM)
1239                                         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);
1240                                 else
1241                                 {
1242                                         if (!cl_particles.integer)
1243                                                 continue;
1244                                         switch (info->particletype)
1245                                         {
1246                                         case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1247                                         case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1248                                         case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1249                                         case pt_blood: if (!cl_particles_blood.integer) continue;break;
1250                                         case pt_rain: if (!cl_particles_rain.integer) continue;break;
1251                                         case pt_snow: if (!cl_particles_snow.integer) continue;break;
1252                                         default: break;
1253                                         }
1254                                         VectorCopy(originmins, trailpos);
1255                                         if (info->trailspacing > 0)
1256                                         {
1257                                                 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1258                                                 trailstep = info->trailspacing / cl_particles_quality.value;
1259                                         }
1260                                         else
1261                                         {
1262                                                 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1263                                                 trailstep = 0;
1264                                         }
1265                                         info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1266                                         for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1267                                         {
1268                                                 if (info->tex[1] > info->tex[0])
1269                                                 {
1270                                                         tex = (int)lhrandom(info->tex[0], info->tex[1]);
1271                                                         tex = min(tex, info->tex[1] - 1);
1272                                                 }
1273                                                 if (!trailstep)
1274                                                 {
1275                                                         trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1276                                                         trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1277                                                         trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1278                                                 }
1279                                                 VectorRandom(rvec);
1280                                                 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);
1281                                                 if (trailstep)
1282                                                         VectorMA(trailpos, trailstep, traildir, trailpos);
1283                                         }
1284                                 }
1285                         }
1286                 }
1287         }
1288         if (!found)
1289                 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1290 }
1291
1292 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)
1293 {
1294         CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1295 }
1296
1297 /*
1298 ===============
1299 CL_EntityParticles
1300 ===============
1301 */
1302 void CL_EntityParticles (const entity_t *ent)
1303 {
1304         int i;
1305         float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1306         static vec3_t avelocities[NUMVERTEXNORMALS];
1307         if (!cl_particles.integer) return;
1308         if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1309
1310         Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1311
1312         if (!avelocities[0][0])
1313                 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1314                         avelocities[0][i] = lhrandom(0, 2.55);
1315
1316         for (i = 0;i < NUMVERTEXNORMALS;i++)
1317         {
1318                 yaw = cl.time * avelocities[i][0];
1319                 pitch = cl.time * avelocities[i][1];
1320                 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1321                 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1322                 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1323                 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);
1324         }
1325 }
1326
1327
1328 void CL_ReadPointFile_f (void)
1329 {
1330         vec3_t org, leakorg;
1331         int r, c, s;
1332         char *pointfile = NULL, *pointfilepos, *t, tchar;
1333         char name[MAX_OSPATH];
1334
1335         if (!cl.worldmodel)
1336                 return;
1337
1338         FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1339         strlcat (name, ".pts", sizeof (name));
1340         pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1341         if (!pointfile)
1342         {
1343                 Con_Printf("Could not open %s\n", name);
1344                 return;
1345         }
1346
1347         Con_Printf("Reading %s...\n", name);
1348         VectorClear(leakorg);
1349         c = 0;
1350         s = 0;
1351         pointfilepos = pointfile;
1352         while (*pointfilepos)
1353         {
1354                 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1355                         pointfilepos++;
1356                 if (!*pointfilepos)
1357                         break;
1358                 t = pointfilepos;
1359                 while (*t && *t != '\n' && *t != '\r')
1360                         t++;
1361                 tchar = *t;
1362                 *t = 0;
1363 #if _MSC_VER >= 1400
1364 #define sscanf sscanf_s
1365 #endif
1366                 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1367                 *t = tchar;
1368                 pointfilepos = t;
1369                 if (r != 3)
1370                         break;
1371                 if (c == 0)
1372                         VectorCopy(org, leakorg);
1373                 c++;
1374
1375                 if (cl.num_particles < cl.max_particles - 3)
1376                 {
1377                         s++;
1378                         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);
1379                 }
1380         }
1381         Mem_Free(pointfile);
1382         VectorCopy(leakorg, org);
1383         Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1384
1385         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);
1386         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);
1387         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);
1388 }
1389
1390 /*
1391 ===============
1392 CL_ParseParticleEffect
1393
1394 Parse an effect out of the server message
1395 ===============
1396 */
1397 void CL_ParseParticleEffect (void)
1398 {
1399         vec3_t org, dir;
1400         int i, count, msgcount, color;
1401
1402         MSG_ReadVector(org, cls.protocol);
1403         for (i=0 ; i<3 ; i++)
1404                 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1405         msgcount = MSG_ReadByte ();
1406         color = MSG_ReadByte ();
1407
1408         if (msgcount == 255)
1409                 count = 1024;
1410         else
1411                 count = msgcount;
1412
1413         CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1414 }
1415
1416 /*
1417 ===============
1418 CL_ParticleExplosion
1419
1420 ===============
1421 */
1422 void CL_ParticleExplosion (const vec3_t org)
1423 {
1424         int i;
1425         trace_t trace;
1426         //vec3_t v;
1427         //vec3_t v2;
1428         R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1429         CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1430
1431         if (cl_particles_quake.integer)
1432         {
1433                 for (i = 0;i < 1024;i++)
1434                 {
1435                         int r, color;
1436                         r = rand()&3;
1437                         if (i & 1)
1438                         {
1439                                 color = particlepalette[ramp1[r]];
1440                                 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);
1441                         }
1442                         else
1443                         {
1444                                 color = particlepalette[ramp2[r]];
1445                                 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);
1446                         }
1447                 }
1448         }
1449         else
1450         {
1451                 i = CL_PointSuperContents(org);
1452                 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1453                 {
1454                         if (cl_particles.integer && cl_particles_bubbles.integer)
1455                                 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1456                                         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);
1457                 }
1458                 else
1459                 {
1460                         if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1461                         {
1462                                 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1463                                 {
1464                                         int k;
1465                                         vec3_t v, v2;
1466                                         for (k = 0;k < 16;k++)
1467                                         {
1468                                                 VectorRandom(v2);
1469                                                 VectorMA(org, 128, v2, v);
1470                                                 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1471                                                 if (trace.fraction >= 0.1)
1472                                                         break;
1473                                         }
1474                                         VectorSubtract(trace.endpos, org, v2);
1475                                         VectorScale(v2, 2.0f, v2);
1476                                         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);
1477                                 }
1478                         }
1479                 }
1480         }
1481
1482         if (cl_particles_explosions_shell.integer)
1483                 R_NewExplosion(org);
1484 }
1485
1486 /*
1487 ===============
1488 CL_ParticleExplosion2
1489
1490 ===============
1491 */
1492 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1493 {
1494         int i, k;
1495         if (!cl_particles.integer) return;
1496
1497         for (i = 0;i < 512 * cl_particles_quality.value;i++)
1498         {
1499                 k = particlepalette[colorStart + (i % colorLength)];
1500                 if (cl_particles_quake.integer)
1501                         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);
1502                 else
1503                         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);
1504         }
1505 }
1506
1507 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1508 {
1509         if (cl_particles_sparks.integer)
1510         {
1511                 sparkcount *= cl_particles_quality.value;
1512                 while(sparkcount-- > 0)
1513                         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);
1514         }
1515 }
1516
1517 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1518 {
1519         if (cl_particles_smoke.integer)
1520         {
1521                 smokecount *= cl_particles_quality.value;
1522                 while(smokecount-- > 0)
1523                         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);
1524         }
1525 }
1526
1527 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)
1528 {
1529         int k;
1530         if (!cl_particles.integer) return;
1531
1532         count = (int)(count * cl_particles_quality.value);
1533         while (count--)
1534         {
1535                 k = particlepalette[colorbase + (rand()&3)];
1536                 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);
1537         }
1538 }
1539
1540 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1541 {
1542         int k;
1543         float minz, maxz, lifetime = 30;
1544         if (!cl_particles.integer) return;
1545         if (dir[2] < 0) // falling
1546         {
1547                 minz = maxs[2] + dir[2] * 0.1;
1548                 maxz = maxs[2];
1549                 if (cl.worldmodel)
1550                         lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1551         }
1552         else // rising??
1553         {
1554                 minz = mins[2];
1555                 maxz = maxs[2] + dir[2] * 0.1;
1556                 if (cl.worldmodel)
1557                         lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1558         }
1559
1560         count = (int)(count * cl_particles_quality.value);
1561
1562         switch(type)
1563         {
1564         case 0:
1565                 if (!cl_particles_rain.integer) break;
1566                 count *= 4; // ick, this should be in the mod or maps?
1567
1568                 while(count--)
1569                 {
1570                         k = particlepalette[colorbase + (rand()&3)];
1571                         if (gamemode == GAME_GOODVSBAD2)
1572                                 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);
1573                         else
1574                                 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);
1575                 }
1576                 break;
1577         case 1:
1578                 if (!cl_particles_snow.integer) break;
1579                 while(count--)
1580                 {
1581                         k = particlepalette[colorbase + (rand()&3)];
1582                         if (gamemode == GAME_GOODVSBAD2)
1583                                 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);
1584                         else
1585                                 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);
1586                 }
1587                 break;
1588         default:
1589                 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1590         }
1591 }
1592
1593 #define MAX_PARTICLETEXTURES 64
1594 // particletexture_t is a rectangle in the particlefonttexture
1595 typedef struct particletexture_s
1596 {
1597         rtexture_t *texture;
1598         float s1, t1, s2, t2;
1599 }
1600 particletexture_t;
1601
1602 static rtexturepool_t *particletexturepool;
1603 static rtexture_t *particlefonttexture;
1604 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1605
1606 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1607 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1608 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1609 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1610
1611 #define PARTICLETEXTURESIZE 64
1612 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1613
1614 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1615 {
1616         float dz, f, dot;
1617         vec3_t normal;
1618         dz = 1 - (dx*dx+dy*dy);
1619         if (dz > 0) // it does hit the sphere
1620         {
1621                 f = 0;
1622                 // back side
1623                 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1624                 VectorNormalize(normal);
1625                 dot = DotProduct(normal, light);
1626                 if (dot > 0.5) // interior reflection
1627                         f += ((dot *  2) - 1);
1628                 else if (dot < -0.5) // exterior reflection
1629                         f += ((dot * -2) - 1);
1630                 // front side
1631                 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1632                 VectorNormalize(normal);
1633                 dot = DotProduct(normal, light);
1634                 if (dot > 0.5) // interior reflection
1635                         f += ((dot *  2) - 1);
1636                 else if (dot < -0.5) // exterior reflection
1637                         f += ((dot * -2) - 1);
1638                 f *= 128;
1639                 f += 16; // just to give it a haze so you can see the outline
1640                 f = bound(0, f, 255);
1641                 return (unsigned char) f;
1642         }
1643         else
1644                 return 0;
1645 }
1646
1647 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1648 {
1649         int basex, basey, y;
1650         basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1651         basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1652         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1653                 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1654 }
1655
1656 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1657 {
1658         int x, y;
1659         float cx, cy, dx, dy, f, iradius;
1660         unsigned char *d;
1661         cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1662         cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1663         iradius = 1.0f / radius;
1664         alpha *= (1.0f / 255.0f);
1665         for (y = 0;y < PARTICLETEXTURESIZE;y++)
1666         {
1667                 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1668                 {
1669                         dx = (x - cx);
1670                         dy = (y - cy);
1671                         f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1672                         if (f > 0)
1673                         {
1674                                 if (f > 1)
1675                                         f = 1;
1676                                 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1677                                 d[0] += (int)(f * (blue  - d[0]));
1678                                 d[1] += (int)(f * (green - d[1]));
1679                                 d[2] += (int)(f * (red   - d[2]));
1680                         }
1681                 }
1682         }
1683 }
1684
1685 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1686 {
1687         int i;
1688         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1689         {
1690                 data[0] = bound(minb, data[0], maxb);
1691                 data[1] = bound(ming, data[1], maxg);
1692                 data[2] = bound(minr, data[2], maxr);
1693         }
1694 }
1695
1696 void particletextureinvert(unsigned char *data)
1697 {
1698         int i;
1699         for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1700         {
1701                 data[0] = 255 - data[0];
1702                 data[1] = 255 - data[1];
1703                 data[2] = 255 - data[2];
1704         }
1705 }
1706
1707 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1708 static void R_InitBloodTextures (unsigned char *particletexturedata)
1709 {
1710         int i, j, k, m;
1711         unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1712
1713         // blood particles
1714         for (i = 0;i < 8;i++)
1715         {
1716                 memset(&data[0][0][0], 255, sizeof(data));
1717                 for (k = 0;k < 24;k++)
1718                         particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1719                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1720                 particletextureinvert(&data[0][0][0]);
1721                 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1722         }
1723
1724         // blood decals
1725         for (i = 0;i < 8;i++)
1726         {
1727                 memset(&data[0][0][0], 255, sizeof(data));
1728                 m = 8;
1729                 for (j = 1;j < 10;j++)
1730                         for (k = min(j, m - 1);k < m;k++)
1731                                 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1732                 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1733                 particletextureinvert(&data[0][0][0]);
1734                 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1735         }
1736
1737 }
1738
1739 //uncomment this to make engine save out particle font to a tga file when run
1740 //#define DUMPPARTICLEFONT
1741
1742 static void R_InitParticleTexture (void)
1743 {
1744         int x, y, d, i, k, m;
1745         float dx, dy, f;
1746         vec3_t light;
1747
1748         // a note: decals need to modulate (multiply) the background color to
1749         // properly darken it (stain), and they need to be able to alpha fade,
1750         // this is a very difficult challenge because it means fading to white
1751         // (no change to background) rather than black (darkening everything
1752         // behind the whole decal polygon), and to accomplish this the texture is
1753         // inverted (dark red blood on white background becomes brilliant cyan
1754         // and white on black background) so we can alpha fade it to black, then
1755         // we invert it again during the blendfunc to make it work...
1756
1757 #ifndef DUMPPARTICLEFONT
1758         particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1759         if (!particlefonttexture)
1760 #endif
1761         {
1762                 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1763                 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1764                 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1765
1766                 // smoke
1767                 for (i = 0;i < 8;i++)
1768                 {
1769                         memset(&data[0][0][0], 255, sizeof(data));
1770                         do
1771                         {
1772                                 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1773
1774                                 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1775                                 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1776                                 m = 0;
1777                                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1778                                 {
1779                                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1780                                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1781                                         {
1782                                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1783                                                 d = (noise2[y][x] - 128) * 3 + 192;
1784                                                 if (d > 0)
1785                                                         d = (int)(d * (1-(dx*dx+dy*dy)));
1786                                                 d = (d * noise1[y][x]) >> 7;
1787                                                 d = bound(0, d, 255);
1788                                                 data[y][x][3] = (unsigned char) d;
1789                                                 if (m < d)
1790                                                         m = d;
1791                                         }
1792                                 }
1793                         }
1794                         while (m < 224);
1795                         setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1796                 }
1797
1798                 // rain splash
1799                 memset(&data[0][0][0], 255, sizeof(data));
1800                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1801                 {
1802                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1803                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1804                         {
1805                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1806                                 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1807                                 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1808                         }
1809                 }
1810                 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1811
1812                 // normal particle
1813                 memset(&data[0][0][0], 255, sizeof(data));
1814                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1815                 {
1816                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1817                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1818                         {
1819                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1820                                 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1821                                 d = bound(0, d, 255);
1822                                 data[y][x][3] = (unsigned char) d;
1823                         }
1824                 }
1825                 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1826
1827                 // rain
1828                 memset(&data[0][0][0], 255, sizeof(data));
1829                 light[0] = 1;light[1] = 1;light[2] = 1;
1830                 VectorNormalize(light);
1831                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1832                 {
1833                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1834                         // stretch upper half of bubble by +50% and shrink lower half by -50%
1835                         // (this gives an elongated teardrop shape)
1836                         if (dy > 0.5f)
1837                                 dy = (dy - 0.5f) * 2.0f;
1838                         else
1839                                 dy = (dy - 0.5f) / 1.5f;
1840                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1841                         {
1842                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1843                                 // shrink bubble width to half
1844                                 dx *= 2.0f;
1845                                 data[y][x][3] = shadebubble(dx, dy, light);
1846                         }
1847                 }
1848                 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1849
1850                 // bubble
1851                 memset(&data[0][0][0], 255, sizeof(data));
1852                 light[0] = 1;light[1] = 1;light[2] = 1;
1853                 VectorNormalize(light);
1854                 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1855                 {
1856                         dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1857                         for (x = 0;x < PARTICLETEXTURESIZE;x++)
1858                         {
1859                                 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1860                                 data[y][x][3] = shadebubble(dx, dy, light);
1861                         }
1862                 }
1863                 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1864
1865                 // Blood particles and blood decals
1866                 R_InitBloodTextures (particletexturedata);
1867
1868                 // bullet decals
1869                 for (i = 0;i < 8;i++)
1870                 {
1871                         memset(&data[0][0][0], 255, sizeof(data));
1872                         for (k = 0;k < 12;k++)
1873                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1874                         for (k = 0;k < 3;k++)
1875                                 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1876                         //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1877                         particletextureinvert(&data[0][0][0]);
1878                         setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1879                 }
1880
1881 #ifdef DUMPPARTICLEFONT
1882                 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1883 #endif
1884
1885                 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1886
1887                 Mem_Free(particletexturedata);
1888         }
1889         for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1890         {
1891                 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1892                 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1893                 particletexture[i].texture = particlefonttexture;
1894                 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1895                 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1896                 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1897                 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1898         }
1899
1900 #ifndef DUMPPARTICLEFONT
1901         particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1902         if (!particletexture[tex_beam].texture)
1903 #endif
1904         {
1905                 unsigned char noise3[64][64], data2[64][16][4];
1906                 // nexbeam
1907                 fractalnoise(&noise3[0][0], 64, 4);
1908                 m = 0;
1909                 for (y = 0;y < 64;y++)
1910                 {
1911                         dy = (y - 0.5f*64) / (64*0.5f-1);
1912                         for (x = 0;x < 16;x++)
1913                         {
1914                                 dx = (x - 0.5f*16) / (16*0.5f-2);
1915                                 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1916                                 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1917                                 data2[y][x][3] = 255;
1918                         }
1919                 }
1920
1921 #ifdef DUMPPARTICLEFONT
1922                 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1923 #endif
1924                 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1925         }
1926         particletexture[tex_beam].s1 = 0;
1927         particletexture[tex_beam].t1 = 0;
1928         particletexture[tex_beam].s2 = 1;
1929         particletexture[tex_beam].t2 = 1;
1930 }
1931
1932 static void r_part_start(void)
1933 {
1934         int i;
1935         // generate particlepalette for convenience from the main one
1936         for (i = 0;i < 256;i++)
1937                 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1938         particletexturepool = R_AllocTexturePool();
1939         R_InitParticleTexture ();
1940         CL_Particles_LoadEffectInfo();
1941 }
1942
1943 static void r_part_shutdown(void)
1944 {
1945         R_FreeTexturePool(&particletexturepool);
1946 }
1947
1948 static void r_part_newmap(void)
1949 {
1950         CL_Particles_LoadEffectInfo();
1951 }
1952
1953 #define BATCHSIZE 256
1954 unsigned short particle_elements[BATCHSIZE*6];
1955
1956 void R_Particles_Init (void)
1957 {
1958         int i;
1959         for (i = 0;i < BATCHSIZE;i++)
1960         {
1961                 particle_elements[i*6+0] = i*4+0;
1962                 particle_elements[i*6+1] = i*4+1;
1963                 particle_elements[i*6+2] = i*4+2;
1964                 particle_elements[i*6+3] = i*4+0;
1965                 particle_elements[i*6+4] = i*4+2;
1966                 particle_elements[i*6+5] = i*4+3;
1967         }
1968
1969         Cvar_RegisterVariable(&r_drawparticles);
1970         Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1971         Cvar_RegisterVariable(&r_drawdecals);
1972         Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1973         R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1974 }
1975
1976 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1977 {
1978         int surfacelistindex;
1979         const decal_t *d;
1980         float *v3f, *t2f, *c4f;
1981         particletexture_t *tex;
1982         float right[3], up[3], size, ca;
1983         float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1984         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1985
1986         r_refdef.stats.decals += numsurfaces;
1987         R_Mesh_Matrix(&identitymatrix);
1988         R_Mesh_ResetTextureState();
1989         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1990         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1991         R_Mesh_ColorPointer(particle_color4f, 0, 0);
1992         R_SetupGenericShader(true);
1993         GL_DepthMask(false);
1994         GL_DepthRange(0, 1);
1995         GL_PolygonOffset(0, 0);
1996         GL_DepthTest(true);
1997         GL_CullFace(GL_NONE);
1998
1999         // generate all the vertices at once
2000         for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2001         {
2002                 d = cl.decals + surfacelist[surfacelistindex];
2003
2004                 // calculate color
2005                 c4f = particle_color4f + 16*surfacelistindex;
2006                 ca = d->alpha * alphascale;
2007                 if (r_refdef.fogenabled)
2008                         ca *= FogPoint_World(d->org);
2009                 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2010                 Vector4Copy(c4f, c4f + 4);
2011                 Vector4Copy(c4f, c4f + 8);
2012                 Vector4Copy(c4f, c4f + 12);
2013
2014                 // calculate vertex positions
2015                 size = d->size * cl_particles_size.value;
2016                 VectorVectors(d->normal, right, up);
2017                 VectorScale(right, size, right);
2018                 VectorScale(up, size, up);
2019                 v3f = particle_vertex3f + 12*surfacelistindex;
2020                 v3f[ 0] = d->org[0] - right[0] - up[0];
2021                 v3f[ 1] = d->org[1] - right[1] - up[1];
2022                 v3f[ 2] = d->org[2] - right[2] - up[2];
2023                 v3f[ 3] = d->org[0] - right[0] + up[0];
2024                 v3f[ 4] = d->org[1] - right[1] + up[1];
2025                 v3f[ 5] = d->org[2] - right[2] + up[2];
2026                 v3f[ 6] = d->org[0] + right[0] + up[0];
2027                 v3f[ 7] = d->org[1] + right[1] + up[1];
2028                 v3f[ 8] = d->org[2] + right[2] + up[2];
2029                 v3f[ 9] = d->org[0] + right[0] - up[0];
2030                 v3f[10] = d->org[1] + right[1] - up[1];
2031                 v3f[11] = d->org[2] + right[2] - up[2];
2032
2033                 // calculate texcoords
2034                 tex = &particletexture[d->texnum];
2035                 t2f = particle_texcoord2f + 8*surfacelistindex;
2036                 t2f[0] = tex->s1;t2f[1] = tex->t2;
2037                 t2f[2] = tex->s1;t2f[3] = tex->t1;
2038                 t2f[4] = tex->s2;t2f[5] = tex->t1;
2039                 t2f[6] = tex->s2;t2f[7] = tex->t2;
2040         }
2041
2042         // now render the decals all at once
2043         // (this assumes they all use one particle font texture!)
2044         GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2045         R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2046         GL_LockArrays(0, numsurfaces*4);
2047         R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2048         GL_LockArrays(0, 0);
2049 }
2050
2051 void R_DrawDecals (void)
2052 {
2053         int i;
2054         decal_t *decal;
2055         float frametime;
2056         float decalfade;
2057         float drawdist2;
2058
2059         frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2060         cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2061
2062         // LordHavoc: early out conditions
2063         if ((!cl.num_decals) || (!r_drawdecals.integer))
2064                 return;
2065
2066         decalfade = frametime * 256 / cl_decals_fadetime.value;
2067         drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2068         drawdist2 = drawdist2*drawdist2;
2069
2070         for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2071         {
2072                 if (!decal->typeindex)
2073                         continue;
2074
2075                 if (cl.time > decal->time2 + cl_decals_time.value)
2076                 {
2077                         decal->alpha -= decalfade;
2078                         if (decal->alpha <= 0)
2079                                 goto killdecal;
2080                 }
2081
2082                 if (decal->owner)
2083                 {
2084                         if (cl.entities[decal->owner].render.model == decal->ownermodel)
2085                         {
2086                                 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2087                                 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2088                         }
2089                         else
2090                                 goto killdecal;
2091                 }
2092
2093                 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))
2094                 {
2095                         if(!cl_particles_novis.integer)
2096                                 if (!r_refdef.viewcache.world_novis)
2097                                         if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2098                                         {
2099                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
2100                                                 if(leaf)
2101                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2102                                                                 continue;
2103                                         }
2104                         R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2105                 }
2106                 continue;
2107 killdecal:
2108                 decal->typeindex = 0;
2109                 if (cl.free_decal > i)
2110                         cl.free_decal = i;
2111         }
2112
2113         // reduce cl.num_decals if possible
2114         while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2115                 cl.num_decals--;
2116
2117         if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2118         {
2119                 decal_t *olddecals = cl.decals;
2120                 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2121                 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2122                 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2123                 Mem_Free(olddecals);
2124         }
2125 }
2126
2127 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2128 {
2129         int surfacelistindex;
2130         int batchstart, batchcount;
2131         const particle_t *p;
2132         pblend_t blendmode;
2133         rtexture_t *texture;
2134         float *v3f, *t2f, *c4f;
2135         particletexture_t *tex;
2136         float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2137         float ambient[3], diffuse[3], diffusenormal[3];
2138         vec4_t colormultiplier;
2139         float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2140
2141         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));
2142
2143         r_refdef.stats.particles += numsurfaces;
2144         R_Mesh_Matrix(&identitymatrix);
2145         R_Mesh_ResetTextureState();
2146         R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2147         R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2148         R_Mesh_ColorPointer(particle_color4f, 0, 0);
2149         R_SetupGenericShader(true);
2150         GL_DepthMask(false);
2151         GL_DepthRange(0, 1);
2152         GL_PolygonOffset(0, 0);
2153         GL_DepthTest(true);
2154         GL_CullFace(GL_NONE);
2155
2156         // first generate all the vertices at once
2157         for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2158         {
2159                 p = cl.particles + surfacelist[surfacelistindex];
2160
2161                 blendmode = p->blendmode;
2162
2163                 c4f[0] = p->color[0] * colormultiplier[0];
2164                 c4f[1] = p->color[1] * colormultiplier[1];
2165                 c4f[2] = p->color[2] * colormultiplier[2];
2166                 c4f[3] = p->alpha * colormultiplier[3];
2167                 switch (blendmode)
2168                 {
2169                 case PBLEND_INVALID:
2170                 case PBLEND_INVMOD:
2171                 case PBLEND_ADD:
2172                         // additive and modulate can just fade out in fog (this is correct)
2173                         if (r_refdef.fogenabled)
2174                                 c4f[3] *= FogPoint_World(p->org);
2175                         // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2176                         c4f[0] *= c4f[3];
2177                         c4f[1] *= c4f[3];
2178                         c4f[2] *= c4f[3];
2179                         c4f[3] = 1;
2180                         break;
2181                 case PBLEND_ALPHA:
2182                         // note: lighting is not cheap!
2183                         if (particletype[p->typeindex].lighting)
2184                         {
2185                                 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2186                                 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2187                                 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2188                                 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2189                         }
2190                         // mix in the fog color
2191                         if (r_refdef.fogenabled)
2192                         {
2193                                 fog = FogPoint_World(p->org);
2194                                 ifog = 1 - fog;
2195                                 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2196                                 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2197                                 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2198                         }
2199                         break;
2200                 }
2201                 // copy the color into the other three vertices
2202                 Vector4Copy(c4f, c4f + 4);
2203                 Vector4Copy(c4f, c4f + 8);
2204                 Vector4Copy(c4f, c4f + 12);
2205
2206                 size = p->size * cl_particles_size.value;
2207                 tex = &particletexture[p->texnum];
2208                 switch(p->orientation)
2209                 {
2210                 case PARTICLE_INVALID:
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 = PBLEND_INVALID;
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_INVALID:
2299                         case PBLEND_ADD:
2300                                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2301                                 break;
2302                         case PBLEND_INVMOD:
2303                                 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2304                                 break;
2305                         }
2306                 }
2307                 if (texture != particletexture[p->texnum].texture)
2308                 {
2309                         texture = particletexture[p->texnum].texture;
2310                         R_Mesh_TexBind(0, R_GetTexture(texture));
2311                 }
2312
2313                 // iterate until we find a change in settings
2314                 batchstart = surfacelistindex++;
2315                 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2316                 {
2317                         p = cl.particles + surfacelist[surfacelistindex];
2318                         if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2319                                 break;
2320                 }
2321
2322                 batchcount = surfacelistindex - batchstart;
2323                 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2324         }
2325         GL_LockArrays(0, 0);
2326 }
2327
2328 void R_DrawParticles (void)
2329 {
2330         int i, a, content;
2331         float minparticledist;
2332         particle_t *p;
2333         float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2334         float drawdist2;
2335         int hitent;
2336         trace_t trace;
2337         qboolean update;
2338
2339         frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2340         cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2341
2342         // LordHavoc: early out conditions
2343         if ((!cl.num_particles) || (!r_drawparticles.integer))
2344                 return;
2345
2346         minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2347         gravity = frametime * cl.movevars_gravity;
2348         dvel = 1+4*frametime;
2349         decalfade = frametime * 255 / cl_decals_fadetime.value;
2350         update = frametime > 0;
2351         drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2352         drawdist2 = drawdist2*drawdist2;
2353
2354         for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2355         {
2356                 if (!p->typeindex)
2357                 {
2358                         if (cl.free_particle > i)
2359                                 cl.free_particle = i;
2360                         continue;
2361                 }
2362
2363                 if (update)
2364                 {
2365                         if (p->delayedspawn > cl.time)
2366                                 continue;
2367                         p->delayedspawn = 0;
2368
2369                         content = 0;
2370
2371                         p->size += p->sizeincrease * frametime;
2372                         p->alpha -= p->alphafade * frametime;
2373
2374                         if (p->alpha <= 0 || p->die <= cl.time)
2375                                 goto killparticle;
2376
2377                         if (p->orientation != PARTICLE_BEAM && frametime > 0)
2378                         {
2379                                 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2380                                 {
2381                                         if (p->typeindex == pt_blood)
2382                                                 p->size += frametime * 8;
2383                                         else
2384                                                 p->vel[2] -= p->gravity * gravity;
2385                                         f = 1.0f - min(p->liquidfriction * frametime, 1);
2386                                         VectorScale(p->vel, f, p->vel);
2387                                 }
2388                                 else
2389                                 {
2390                                         p->vel[2] -= p->gravity * gravity;
2391                                         if (p->airfriction)
2392                                         {
2393                                                 f = 1.0f - min(p->airfriction * frametime, 1);
2394                                                 VectorScale(p->vel, f, p->vel);
2395                                         }
2396                                 }
2397
2398                                 VectorCopy(p->org, oldorg);
2399                                 VectorMA(p->org, frametime, p->vel, p->org);
2400                                 if (p->bounce && cl.time >= p->delayedcollisions)
2401                                 {
2402                                         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);
2403                                         // if the trace started in or hit something of SUPERCONTENTS_NODROP
2404                                         // or if the trace hit something flagged as NOIMPACT
2405                                         // then remove the particle
2406                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2407                                                 goto killparticle;
2408                                         VectorCopy(trace.endpos, p->org);
2409                                         // react if the particle hit something
2410                                         if (trace.fraction < 1)
2411                                         {
2412                                                 VectorCopy(trace.endpos, p->org);
2413                                                 if (p->typeindex == pt_blood)
2414                                                 {
2415                                                         // blood - splash on solid
2416                                                         if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2417                                                                 goto killparticle;
2418                                                         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)));
2419                                                         if (cl_decals.integer)
2420                                                         {
2421                                                                 // create a decal for the blood splat
2422                                                                 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);
2423                                                         }
2424                                                         goto killparticle;
2425                                                 }
2426                                                 else if (p->bounce < 0)
2427                                                 {
2428                                                         // bounce -1 means remove on impact
2429                                                         goto killparticle;
2430                                                 }
2431                                                 else
2432                                                 {
2433                                                         // anything else - bounce off solid
2434                                                         dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2435                                                         VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2436                                                         if (DotProduct(p->vel, p->vel) < 0.03)
2437                                                                 VectorClear(p->vel);
2438                                                 }
2439                                         }
2440                                 }
2441                         }
2442
2443                         if (p->typeindex != pt_static)
2444                         {
2445                                 switch (p->typeindex)
2446                                 {
2447                                 case pt_entityparticle:
2448                                         // particle that removes itself after one rendered frame
2449                                         if (p->time2)
2450                                                 goto killparticle;
2451                                         else
2452                                                 p->time2 = 1;
2453                                         break;
2454                                 case pt_blood:
2455                                         a = CL_PointSuperContents(p->org);
2456                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2457                                                 goto killparticle;
2458                                         break;
2459                                 case pt_bubble:
2460                                         a = CL_PointSuperContents(p->org);
2461                                         if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2462                                                 goto killparticle;
2463                                         break;
2464                                 case pt_rain:
2465                                         a = CL_PointSuperContents(p->org);
2466                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2467                                                 goto killparticle;
2468                                         break;
2469                                 case pt_snow:
2470                                         if (cl.time > p->time2)
2471                                         {
2472                                                 // snow flutter
2473                                                 p->time2 = cl.time + (rand() & 3) * 0.1;
2474                                                 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2475                                                 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2476                                         }
2477                                         a = CL_PointSuperContents(p->org);
2478                                         if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2479                                                 goto killparticle;
2480                                         break;
2481                                 default:
2482                                         break;
2483                                 }
2484                         }
2485                 }
2486                 else if (p->delayedspawn)
2487                         continue;
2488
2489                 // don't render particles too close to the view (they chew fillrate)
2490                 // also don't render particles behind the view (useless)
2491                 // further checks to cull to the frustum would be too slow here
2492                 switch(p->typeindex)
2493                 {
2494                 case pt_beam:
2495                         // beams have no culling
2496                         R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2497                         break;
2498                 default:
2499                         if(!cl_particles_novis.integer)
2500                                 if (!r_refdef.viewcache.world_novis)
2501                                         if(r_refdef.scene.worldmodel->brush.PointInLeaf)
2502                                         {
2503                                                 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2504                                                 if(leaf)
2505                                                         if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2506                                                                 continue;
2507                                         }
2508                         // anything else just has to be in front of the viewer and visible at this distance
2509                         if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2510                                 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2511                         break;
2512                 }
2513
2514                 continue;
2515 killparticle:
2516                 p->typeindex = 0;
2517                 if (cl.free_particle > i)
2518                         cl.free_particle = i;
2519         }
2520
2521         // reduce cl.num_particles if possible
2522         while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2523                 cl.num_particles--;
2524
2525         if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2526         {
2527                 particle_t *oldparticles = cl.particles;
2528                 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2529                 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2530                 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2531                 Mem_Free(oldparticles);
2532         }
2533 }