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