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