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