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