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