2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 // texture numbers in particle font
173 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
174 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
175 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
176 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
177 static const int tex_rainsplash = 32;
178 static const int tex_particle = 63;
179 static const int tex_bubble = 62;
180 static const int tex_raindrop = 61;
181 static const int tex_beam = 60;
183 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
184 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
185 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
186 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
187 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
188 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
189 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
190 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
191 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
192 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
193 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
194 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
195 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
196 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
197 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
198 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
199 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
200 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
201 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
202 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
203 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
204 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
205 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
208 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
214 particleeffectinfo_t *info = NULL;
215 const char *text = textstart;
217 effectinfoindex = -1;
218 for (linenumber = 1;;linenumber++)
221 for (arrayindex = 0;arrayindex < 16;arrayindex++)
222 argv[arrayindex][0] = 0;
225 if (!COM_ParseToken_Simple(&text, true, false))
227 if (!strcmp(com_token, "\n"))
231 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
237 #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;}
238 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
239 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
240 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
241 #define readfloat(var) checkparms(2);var = atof(argv[1])
242 if (!strcmp(argv[0], "effect"))
247 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
249 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
252 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
254 if (particleeffectname[effectnameindex][0])
256 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
261 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
265 // if we run out of names, abort
266 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
268 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
271 info = particleeffectinfo + effectinfoindex;
272 info->effectnameindex = effectnameindex;
273 info->particletype = pt_alphastatic;
274 info->blendmode = particletype[info->particletype].blendmode;
275 info->orientation = particletype[info->particletype].orientation;
276 info->tex[0] = tex_particle;
277 info->tex[1] = tex_particle;
278 info->color[0] = 0xFFFFFF;
279 info->color[1] = 0xFFFFFF;
283 info->alpha[1] = 256;
284 info->alpha[2] = 256;
285 info->time[0] = 9999;
286 info->time[1] = 9999;
287 VectorSet(info->lightcolor, 1, 1, 1);
288 info->lightshadow = true;
289 info->lighttime = 9999;
290 info->stretchfactor = 1;
291 info->staincolor[0] = -1;
292 info->staincolor[1] = -1;
293 info->staintex[0] = -1;
294 info->staintex[1] = -1;
296 else if (info == NULL)
298 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
301 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
302 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
303 else if (!strcmp(argv[0], "type"))
306 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
307 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
308 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
309 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
310 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
311 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
312 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
313 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
314 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
315 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
316 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
317 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
318 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
319 info->blendmode = particletype[info->particletype].blendmode;
320 info->orientation = particletype[info->particletype].orientation;
322 else if (!strcmp(argv[0], "blend"))
325 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
326 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
327 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
328 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
330 else if (!strcmp(argv[0], "orientation"))
333 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
334 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
335 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
336 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_BEAM;
337 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
339 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
340 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
341 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
342 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
343 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
344 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
345 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
346 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
347 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
348 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
349 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
350 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
351 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
352 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
353 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
354 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
355 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
356 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
357 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
358 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
359 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
360 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
361 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
362 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
363 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
364 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
365 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
366 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = -1; info->staincolor[1] = -1;}
368 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
377 int CL_ParticleEffectIndexForName(const char *name)
380 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
381 if (!strcmp(particleeffectname[i], name))
386 const char *CL_ParticleEffectNameForIndex(int i)
388 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
390 return particleeffectname[i];
393 // MUST match effectnameindex_t in client.h
394 static const char *standardeffectnames[EFFECT_TOTAL] =
418 "TE_TEI_BIGEXPLOSION",
434 void CL_Particles_LoadEffectInfo(void)
437 unsigned char *filedata;
438 fs_offset_t filesize;
439 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
440 memset(particleeffectname, 0, sizeof(particleeffectname));
441 for (i = 0;i < EFFECT_TOTAL;i++)
442 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
443 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
446 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
456 void CL_ReadPointFile_f (void);
457 void CL_Particles_Init (void)
459 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)");
460 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
462 Cvar_RegisterVariable (&cl_particles);
463 Cvar_RegisterVariable (&cl_particles_quality);
464 Cvar_RegisterVariable (&cl_particles_alpha);
465 Cvar_RegisterVariable (&cl_particles_size);
466 Cvar_RegisterVariable (&cl_particles_quake);
467 Cvar_RegisterVariable (&cl_particles_blood);
468 Cvar_RegisterVariable (&cl_particles_blood_alpha);
469 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
470 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
471 Cvar_RegisterVariable (&cl_particles_explosions_shell);
472 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
473 Cvar_RegisterVariable (&cl_particles_rain);
474 Cvar_RegisterVariable (&cl_particles_snow);
475 Cvar_RegisterVariable (&cl_particles_smoke);
476 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
477 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
478 Cvar_RegisterVariable (&cl_particles_sparks);
479 Cvar_RegisterVariable (&cl_particles_bubbles);
480 Cvar_RegisterVariable (&cl_particles_visculling);
481 Cvar_RegisterVariable (&cl_decals);
482 Cvar_RegisterVariable (&cl_decals_visculling);
483 Cvar_RegisterVariable (&cl_decals_time);
484 Cvar_RegisterVariable (&cl_decals_fadetime);
487 void CL_Particles_Shutdown (void)
491 // list of all 26 parameters:
492 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
493 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
494 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
495 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
496 // palpha - opacity of particle as 0-255 (can be more than 255)
497 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
498 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
499 // pgravity - how much effect gravity has on the particle (0-1)
500 // 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
501 // px,py,pz - starting origin of particle
502 // pvx,pvy,pvz - starting velocity of particle
503 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
504 // blendmode - one of the PBLEND_ values
505 // orientation - one of the PARTICLE_ values
506 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
507 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
508 static particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex)
513 if (!cl_particles.integer)
515 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
516 if (cl.free_particle >= cl.max_particles)
519 lifetime = palpha / min(1, palphafade);
520 part = &cl.particles[cl.free_particle++];
521 if (cl.num_particles < cl.free_particle)
522 cl.num_particles = cl.free_particle;
523 memset(part, 0, sizeof(*part));
524 part->typeindex = ptypeindex;
525 part->blendmode = blendmode;
526 part->orientation = orientation;
527 l2 = (int)lhrandom(0.5, 256.5);
529 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
530 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
531 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
532 part->staintexnum = staintex;
533 if(staincolor1 >= 0 && staincolor2 >= 0)
535 l2 = (int)lhrandom(0.5, 256.5);
537 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
538 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
539 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[1]) / 0x8000;
540 if(r > 0xFF) r = 0xFF;
541 if(g > 0xFF) g = 0xFF;
542 if(b > 0xFF) b = 0xFF;
546 r = part->color[0]; // -1 is shorthand for stain = particle color
550 part->staincolor = (r * 65536 + g * 256 + b) ^ 0xFFFFFF; // inverted, as decals draw in inverted color (subtractive)!
553 part->sizeincrease = psizeincrease;
554 part->alpha = palpha;
555 part->alphafade = palphafade;
556 part->gravity = pgravity;
557 part->bounce = pbounce;
558 part->stretch = stretch;
560 part->org[0] = px + originjitter * v[0];
561 part->org[1] = py + originjitter * v[1];
562 part->org[2] = pz + originjitter * v[2];
563 part->vel[0] = pvx + velocityjitter * v[0];
564 part->vel[1] = pvy + velocityjitter * v[1];
565 part->vel[2] = pvz + velocityjitter * v[2];
567 part->airfriction = pairfriction;
568 part->liquidfriction = pliquidfriction;
569 part->die = cl.time + lifetime;
570 part->delayedcollisions = 0;
571 part->qualityreduction = pqualityreduction;
572 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
573 if (part->typeindex == pt_rain)
577 float lifetime = part->die - cl.time;
580 // turn raindrop into simple spark and create delayedspawn splash effect
581 part->typeindex = pt_spark;
583 VectorMA(part->org, lifetime, part->vel, endvec);
584 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
585 part->die = cl.time + lifetime * trace.fraction;
586 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1);
589 part2->delayedspawn = part->die;
590 part2->die += part->die - cl.time;
591 for (i = rand() & 7;i < 10;i++)
593 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
596 part2->delayedspawn = part->die;
597 part2->die += part->die - cl.time;
602 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
604 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
607 VectorMA(part->org, lifetime, part->vel, endvec);
608 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
609 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
614 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
618 if (!cl_decals.integer)
620 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
621 if (cl.free_decal >= cl.max_decals)
623 decal = &cl.decals[cl.free_decal++];
624 if (cl.num_decals < cl.free_decal)
625 cl.num_decals = cl.free_decal;
626 memset(decal, 0, sizeof(*decal));
627 decal->typeindex = pt_decal;
628 decal->texnum = texnum;
629 VectorAdd(org, normal, decal->org);
630 VectorCopy(normal, decal->normal);
632 decal->alpha = alpha;
633 decal->time2 = cl.time;
634 l2 = (int)lhrandom(0.5, 256.5);
636 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
637 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
638 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
639 decal->owner = hitent;
640 decal->clusterindex = -1000; // no vis culling unless we're sure
643 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
644 decal->ownermodel = cl.entities[decal->owner].render.model;
645 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
646 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
650 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
652 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
654 decal->clusterindex = leaf->clusterindex;
659 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
662 float bestfrac, bestorg[3], bestnormal[3];
664 int besthitent = 0, hitent;
667 for (i = 0;i < 32;i++)
670 VectorMA(org, maxdist, org2, org2);
671 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
672 // take the closest trace result that doesn't end up hitting a NOMARKS
673 // surface (sky for example)
674 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
676 bestfrac = trace.fraction;
678 VectorCopy(trace.endpos, bestorg);
679 VectorCopy(trace.plane.normal, bestnormal);
683 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
686 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
687 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
688 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)
691 matrix4x4_t tempmatrix;
692 VectorLerp(originmins, 0.5, originmaxs, center);
693 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
694 if (effectnameindex == EFFECT_SVC_PARTICLE)
696 if (cl_particles.integer)
698 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
700 CL_ParticleExplosion(center);
701 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
702 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
705 count *= cl_particles_quality.value;
706 for (;count > 0;count--)
708 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
709 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
714 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
715 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
716 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
717 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
718 else if (effectnameindex == EFFECT_TE_SPIKE)
720 if (cl_particles_bulletimpacts.integer)
722 if (cl_particles_quake.integer)
724 if (cl_particles_smoke.integer)
725 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
729 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
730 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
731 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
735 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
736 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
738 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
740 if (cl_particles_bulletimpacts.integer)
742 if (cl_particles_quake.integer)
744 if (cl_particles_smoke.integer)
745 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
749 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
750 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
751 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
755 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
756 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
757 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
759 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
761 if (cl_particles_bulletimpacts.integer)
763 if (cl_particles_quake.integer)
765 if (cl_particles_smoke.integer)
766 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
770 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
771 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
772 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
776 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
777 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
779 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
781 if (cl_particles_bulletimpacts.integer)
783 if (cl_particles_quake.integer)
785 if (cl_particles_smoke.integer)
786 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
790 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
791 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
792 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
796 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
797 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
798 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);
800 else if (effectnameindex == EFFECT_TE_BLOOD)
802 if (!cl_particles_blood.integer)
804 if (cl_particles_quake.integer)
805 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
808 static double bloodaccumulator = 0;
809 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
810 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
811 for (;bloodaccumulator > 0;bloodaccumulator--)
812 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
815 else if (effectnameindex == EFFECT_TE_SPARK)
816 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
817 else if (effectnameindex == EFFECT_TE_PLASMABURN)
819 // plasma scorch mark
820 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
821 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
822 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
824 else if (effectnameindex == EFFECT_TE_GUNSHOT)
826 if (cl_particles_bulletimpacts.integer)
828 if (cl_particles_quake.integer)
829 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
832 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
833 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
834 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
838 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
839 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
841 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
843 if (cl_particles_bulletimpacts.integer)
845 if (cl_particles_quake.integer)
846 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
849 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
850 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
851 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
855 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
856 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
857 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);
859 else if (effectnameindex == EFFECT_TE_EXPLOSION)
861 CL_ParticleExplosion(center);
862 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);
864 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
866 CL_ParticleExplosion(center);
867 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);
869 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
871 if (cl_particles_quake.integer)
874 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
877 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
879 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
883 CL_ParticleExplosion(center);
884 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);
886 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
887 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);
888 else if (effectnameindex == EFFECT_TE_FLAMEJET)
890 count *= cl_particles_quality.value;
892 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
894 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
896 float i, j, inc, vel;
899 inc = 8 / cl_particles_quality.value;
900 for (i = -128;i < 128;i += inc)
902 for (j = -128;j < 128;j += inc)
904 dir[0] = j + lhrandom(0, inc);
905 dir[1] = i + lhrandom(0, inc);
907 org[0] = center[0] + dir[0];
908 org[1] = center[1] + dir[1];
909 org[2] = center[2] + lhrandom(0, 64);
910 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
911 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
915 else if (effectnameindex == EFFECT_TE_TELEPORT)
917 float i, j, k, inc, vel;
920 if (cl_particles_quake.integer)
921 inc = 4 / cl_particles_quality.value;
923 inc = 8 / cl_particles_quality.value;
924 for (i = -16;i < 16;i += inc)
926 for (j = -16;j < 16;j += inc)
928 for (k = -24;k < 32;k += inc)
930 VectorSet(dir, i*8, j*8, k*8);
931 VectorNormalize(dir);
932 vel = lhrandom(50, 113);
933 if (cl_particles_quake.integer)
934 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
936 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
940 if (!cl_particles_quake.integer)
941 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
942 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);
944 else if (effectnameindex == EFFECT_TE_TEI_G3)
945 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
946 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
948 if (cl_particles_smoke.integer)
950 count *= 0.25f * cl_particles_quality.value;
952 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
955 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
957 CL_ParticleExplosion(center);
958 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);
960 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
963 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
964 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
965 if (cl_particles_smoke.integer)
966 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
967 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
968 if (cl_particles_sparks.integer)
969 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
970 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
971 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);
973 else if (effectnameindex == EFFECT_EF_FLAME)
975 count *= 300 * cl_particles_quality.value;
977 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
978 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);
980 else if (effectnameindex == EFFECT_EF_STARDUST)
982 count *= 200 * cl_particles_quality.value;
984 CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
985 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);
987 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
991 int smoke, blood, bubbles, r, color;
993 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
996 Vector4Set(light, 0, 0, 0, 0);
998 if (effectnameindex == EFFECT_TR_ROCKET)
999 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1000 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1002 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1003 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1005 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1007 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1008 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1012 matrix4x4_t tempmatrix;
1013 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1014 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1015 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1019 if (!spawnparticles)
1022 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1025 VectorSubtract(originmaxs, originmins, dir);
1026 len = VectorNormalizeLength(dir);
1029 dec = -ent->persistent.trail_time;
1030 ent->persistent.trail_time += len;
1031 if (ent->persistent.trail_time < 0.01f)
1034 // if we skip out, leave it reset
1035 ent->persistent.trail_time = 0.0f;
1040 // advance into this frame to reach the first puff location
1041 VectorMA(originmins, dec, dir, pos);
1044 smoke = cl_particles.integer && cl_particles_smoke.integer;
1045 blood = cl_particles.integer && cl_particles_blood.integer;
1046 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1047 qd = 1.0f / cl_particles_quality.value;
1054 if (effectnameindex == EFFECT_TR_BLOOD)
1056 if (cl_particles_quake.integer)
1058 color = particlepalette[67 + (rand()&3)];
1059 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1064 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1067 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1069 if (cl_particles_quake.integer)
1072 color = particlepalette[67 + (rand()&3)];
1073 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1078 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1084 if (effectnameindex == EFFECT_TR_ROCKET)
1086 if (cl_particles_quake.integer)
1089 color = particlepalette[ramp3[r]];
1090 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1094 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1095 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1098 else if (effectnameindex == EFFECT_TR_GRENADE)
1100 if (cl_particles_quake.integer)
1103 color = particlepalette[ramp3[r]];
1104 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1108 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1111 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1113 if (cl_particles_quake.integer)
1116 color = particlepalette[52 + (rand()&7)];
1117 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1118 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1120 else if (gamemode == GAME_GOODVSBAD2)
1123 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1127 color = particlepalette[20 + (rand()&7)];
1128 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1131 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1133 if (cl_particles_quake.integer)
1136 color = particlepalette[230 + (rand()&7)];
1137 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1138 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1142 color = particlepalette[226 + (rand()&7)];
1143 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1146 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1148 if (cl_particles_quake.integer)
1150 color = particlepalette[152 + (rand()&3)];
1151 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1153 else if (gamemode == GAME_GOODVSBAD2)
1156 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1158 else if (gamemode == GAME_PRYDON)
1161 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1164 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1166 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1169 CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1171 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1174 CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1176 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1177 CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1181 if (effectnameindex == EFFECT_TR_ROCKET)
1182 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1183 else if (effectnameindex == EFFECT_TR_GRENADE)
1184 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1186 // advance to next time and position
1189 VectorMA (pos, dec, dir, pos);
1192 ent->persistent.trail_time = len;
1194 else if (developer.integer >= 1)
1195 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1198 // this is also called on point effects with spawndlight = true and
1199 // spawnparticles = true
1200 // it is called CL_ParticleTrail because most code does not want to supply
1201 // these parameters, only trail handling does
1202 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)
1205 qboolean found = false;
1206 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1208 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1209 return; // no such effect
1211 VectorLerp(originmins, 0.5, originmaxs, center);
1212 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1214 int effectinfoindex;
1217 particleeffectinfo_t *info;
1219 vec3_t centervelocity;
1225 qboolean underwater;
1226 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1227 VectorLerp(originmins, 0.5, originmaxs, center);
1228 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1229 supercontents = CL_PointSuperContents(center);
1230 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1231 VectorSubtract(originmaxs, originmins, traildir);
1232 traillen = VectorLength(traildir);
1233 VectorNormalize(traildir);
1234 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1236 if (info->effectnameindex == effectnameindex)
1239 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1241 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1244 // spawn a dlight if requested
1245 if (info->lightradiusstart > 0 && spawndlight)
1247 matrix4x4_t tempmatrix;
1248 if (info->trailspacing > 0)
1249 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1251 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1252 if (info->lighttime > 0 && info->lightradiusfade > 0)
1254 // light flash (explosion, etc)
1255 // called when effect starts
1256 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);
1261 // called by CL_LinkNetworkEntity
1262 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1263 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1264 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1268 if (!spawnparticles)
1273 if (info->tex[1] > info->tex[0])
1275 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1276 tex = min(tex, info->tex[1] - 1);
1278 if(info->staintex[0] < 0)
1279 staintex = info->staintex[0];
1282 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1283 staintex = min(staintex, info->staintex[1] - 1);
1285 if (info->particletype == pt_decal)
1286 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]);
1287 else if (info->orientation == PARTICLE_BEAM)
1288 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1291 if (!cl_particles.integer)
1293 switch (info->particletype)
1295 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1296 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1297 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1298 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1299 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1300 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1303 VectorCopy(originmins, trailpos);
1304 if (info->trailspacing > 0)
1306 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1307 trailstep = info->trailspacing / cl_particles_quality.value;
1311 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1314 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1315 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1317 if (info->tex[1] > info->tex[0])
1319 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1320 tex = min(tex, info->tex[1] - 1);
1324 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1325 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1326 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1329 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1331 VectorMA(trailpos, trailstep, traildir, trailpos);
1338 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1341 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)
1343 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1351 void CL_EntityParticles (const entity_t *ent)
1354 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1355 static vec3_t avelocities[NUMVERTEXNORMALS];
1356 if (!cl_particles.integer) return;
1357 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1359 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1361 if (!avelocities[0][0])
1362 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1363 avelocities[0][i] = lhrandom(0, 2.55);
1365 for (i = 0;i < NUMVERTEXNORMALS;i++)
1367 yaw = cl.time * avelocities[i][0];
1368 pitch = cl.time * avelocities[i][1];
1369 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1370 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1371 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1372 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1377 void CL_ReadPointFile_f (void)
1379 vec3_t org, leakorg;
1381 char *pointfile = NULL, *pointfilepos, *t, tchar;
1382 char name[MAX_OSPATH];
1387 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1388 strlcat (name, ".pts", sizeof (name));
1389 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1392 Con_Printf("Could not open %s\n", name);
1396 Con_Printf("Reading %s...\n", name);
1397 VectorClear(leakorg);
1400 pointfilepos = pointfile;
1401 while (*pointfilepos)
1403 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1408 while (*t && *t != '\n' && *t != '\r')
1412 #if _MSC_VER >= 1400
1413 #define sscanf sscanf_s
1415 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1421 VectorCopy(org, leakorg);
1424 if (cl.num_particles < cl.max_particles - 3)
1427 CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1430 Mem_Free(pointfile);
1431 VectorCopy(leakorg, org);
1432 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1434 CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1435 CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1436 CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1441 CL_ParseParticleEffect
1443 Parse an effect out of the server message
1446 void CL_ParseParticleEffect (void)
1449 int i, count, msgcount, color;
1451 MSG_ReadVector(org, cls.protocol);
1452 for (i=0 ; i<3 ; i++)
1453 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1454 msgcount = MSG_ReadByte ();
1455 color = MSG_ReadByte ();
1457 if (msgcount == 255)
1462 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1467 CL_ParticleExplosion
1471 void CL_ParticleExplosion (const vec3_t org)
1477 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1478 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1480 if (cl_particles_quake.integer)
1482 for (i = 0;i < 1024;i++)
1488 color = particlepalette[ramp1[r]];
1489 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1493 color = particlepalette[ramp2[r]];
1494 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1500 i = CL_PointSuperContents(org);
1501 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1503 if (cl_particles.integer && cl_particles_bubbles.integer)
1504 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1505 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1509 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1511 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1515 for (k = 0;k < 16;k++)
1518 VectorMA(org, 128, v2, v);
1519 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1520 if (trace.fraction >= 0.1)
1523 VectorSubtract(trace.endpos, org, v2);
1524 VectorScale(v2, 2.0f, v2);
1525 CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1531 if (cl_particles_explosions_shell.integer)
1532 R_NewExplosion(org);
1537 CL_ParticleExplosion2
1541 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1544 if (!cl_particles.integer) return;
1546 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1548 k = particlepalette[colorStart + (i % colorLength)];
1549 if (cl_particles_quake.integer)
1550 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1552 CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1556 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1558 if (cl_particles_sparks.integer)
1560 sparkcount *= cl_particles_quality.value;
1561 while(sparkcount-- > 0)
1562 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1566 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1568 if (cl_particles_smoke.integer)
1570 smokecount *= cl_particles_quality.value;
1571 while(smokecount-- > 0)
1572 CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1576 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)
1579 if (!cl_particles.integer) return;
1581 count = (int)(count * cl_particles_quality.value);
1584 k = particlepalette[colorbase + (rand()&3)];
1585 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1589 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1592 float minz, maxz, lifetime = 30;
1593 if (!cl_particles.integer) return;
1594 if (dir[2] < 0) // falling
1596 minz = maxs[2] + dir[2] * 0.1;
1599 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1604 maxz = maxs[2] + dir[2] * 0.1;
1606 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1609 count = (int)(count * cl_particles_quality.value);
1614 if (!cl_particles_rain.integer) break;
1615 count *= 4; // ick, this should be in the mod or maps?
1619 k = particlepalette[colorbase + (rand()&3)];
1620 if (gamemode == GAME_GOODVSBAD2)
1621 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1623 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1627 if (!cl_particles_snow.integer) break;
1630 k = particlepalette[colorbase + (rand()&3)];
1631 if (gamemode == GAME_GOODVSBAD2)
1632 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1634 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1638 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1642 #define MAX_PARTICLETEXTURES 64
1643 // particletexture_t is a rectangle in the particlefonttexture
1644 typedef struct particletexture_s
1646 rtexture_t *texture;
1647 float s1, t1, s2, t2;
1651 static rtexturepool_t *particletexturepool;
1652 static rtexture_t *particlefonttexture;
1653 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1655 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1656 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1657 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1658 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1660 #define PARTICLETEXTURESIZE 64
1661 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1663 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1667 dz = 1 - (dx*dx+dy*dy);
1668 if (dz > 0) // it does hit the sphere
1672 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1673 VectorNormalize(normal);
1674 dot = DotProduct(normal, light);
1675 if (dot > 0.5) // interior reflection
1676 f += ((dot * 2) - 1);
1677 else if (dot < -0.5) // exterior reflection
1678 f += ((dot * -2) - 1);
1680 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1681 VectorNormalize(normal);
1682 dot = DotProduct(normal, light);
1683 if (dot > 0.5) // interior reflection
1684 f += ((dot * 2) - 1);
1685 else if (dot < -0.5) // exterior reflection
1686 f += ((dot * -2) - 1);
1688 f += 16; // just to give it a haze so you can see the outline
1689 f = bound(0, f, 255);
1690 return (unsigned char) f;
1696 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1698 int basex, basey, y;
1699 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1700 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1701 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1702 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1705 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1708 float cx, cy, dx, dy, f, iradius;
1710 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1711 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1712 iradius = 1.0f / radius;
1713 alpha *= (1.0f / 255.0f);
1714 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1716 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1720 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1725 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1726 d[0] += (int)(f * (blue - d[0]));
1727 d[1] += (int)(f * (green - d[1]));
1728 d[2] += (int)(f * (red - d[2]));
1734 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1737 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1739 data[0] = bound(minb, data[0], maxb);
1740 data[1] = bound(ming, data[1], maxg);
1741 data[2] = bound(minr, data[2], maxr);
1745 void particletextureinvert(unsigned char *data)
1748 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1750 data[0] = 255 - data[0];
1751 data[1] = 255 - data[1];
1752 data[2] = 255 - data[2];
1756 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1757 static void R_InitBloodTextures (unsigned char *particletexturedata)
1760 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1763 for (i = 0;i < 8;i++)
1765 memset(&data[0][0][0], 255, sizeof(data));
1766 for (k = 0;k < 24;k++)
1767 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1768 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1769 particletextureinvert(&data[0][0][0]);
1770 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1774 for (i = 0;i < 8;i++)
1776 memset(&data[0][0][0], 255, sizeof(data));
1778 for (j = 1;j < 10;j++)
1779 for (k = min(j, m - 1);k < m;k++)
1780 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1781 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1782 particletextureinvert(&data[0][0][0]);
1783 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1788 //uncomment this to make engine save out particle font to a tga file when run
1789 //#define DUMPPARTICLEFONT
1791 static void R_InitParticleTexture (void)
1793 int x, y, d, i, k, m;
1797 // a note: decals need to modulate (multiply) the background color to
1798 // properly darken it (stain), and they need to be able to alpha fade,
1799 // this is a very difficult challenge because it means fading to white
1800 // (no change to background) rather than black (darkening everything
1801 // behind the whole decal polygon), and to accomplish this the texture is
1802 // inverted (dark red blood on white background becomes brilliant cyan
1803 // and white on black background) so we can alpha fade it to black, then
1804 // we invert it again during the blendfunc to make it work...
1806 #ifndef DUMPPARTICLEFONT
1807 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1808 if (!particlefonttexture)
1811 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1812 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1813 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1816 for (i = 0;i < 8;i++)
1818 memset(&data[0][0][0], 255, sizeof(data));
1821 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1823 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1824 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1826 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1828 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1829 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1831 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1832 d = (noise2[y][x] - 128) * 3 + 192;
1834 d = (int)(d * (1-(dx*dx+dy*dy)));
1835 d = (d * noise1[y][x]) >> 7;
1836 d = bound(0, d, 255);
1837 data[y][x][3] = (unsigned char) d;
1844 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1848 memset(&data[0][0][0], 255, sizeof(data));
1849 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1851 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1852 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1854 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1855 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1856 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1859 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1862 memset(&data[0][0][0], 255, sizeof(data));
1863 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1865 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1866 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1868 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1869 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1870 d = bound(0, d, 255);
1871 data[y][x][3] = (unsigned char) d;
1874 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1877 memset(&data[0][0][0], 255, sizeof(data));
1878 light[0] = 1;light[1] = 1;light[2] = 1;
1879 VectorNormalize(light);
1880 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1882 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1883 // stretch upper half of bubble by +50% and shrink lower half by -50%
1884 // (this gives an elongated teardrop shape)
1886 dy = (dy - 0.5f) * 2.0f;
1888 dy = (dy - 0.5f) / 1.5f;
1889 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1891 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1892 // shrink bubble width to half
1894 data[y][x][3] = shadebubble(dx, dy, light);
1897 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1900 memset(&data[0][0][0], 255, sizeof(data));
1901 light[0] = 1;light[1] = 1;light[2] = 1;
1902 VectorNormalize(light);
1903 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1905 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1906 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1908 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1909 data[y][x][3] = shadebubble(dx, dy, light);
1912 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1914 // Blood particles and blood decals
1915 R_InitBloodTextures (particletexturedata);
1918 for (i = 0;i < 8;i++)
1920 memset(&data[0][0][0], 255, sizeof(data));
1921 for (k = 0;k < 12;k++)
1922 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1923 for (k = 0;k < 3;k++)
1924 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1925 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1926 particletextureinvert(&data[0][0][0]);
1927 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1930 #ifdef DUMPPARTICLEFONT
1931 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1934 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1936 Mem_Free(particletexturedata);
1938 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1940 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1941 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1942 particletexture[i].texture = particlefonttexture;
1943 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1944 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1945 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1946 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1949 #ifndef DUMPPARTICLEFONT
1950 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1951 if (!particletexture[tex_beam].texture)
1954 unsigned char noise3[64][64], data2[64][16][4];
1956 fractalnoise(&noise3[0][0], 64, 4);
1958 for (y = 0;y < 64;y++)
1960 dy = (y - 0.5f*64) / (64*0.5f-1);
1961 for (x = 0;x < 16;x++)
1963 dx = (x - 0.5f*16) / (16*0.5f-2);
1964 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1965 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1966 data2[y][x][3] = 255;
1970 #ifdef DUMPPARTICLEFONT
1971 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1973 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1975 particletexture[tex_beam].s1 = 0;
1976 particletexture[tex_beam].t1 = 0;
1977 particletexture[tex_beam].s2 = 1;
1978 particletexture[tex_beam].t2 = 1;
1981 static void r_part_start(void)
1984 // generate particlepalette for convenience from the main one
1985 for (i = 0;i < 256;i++)
1986 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1987 particletexturepool = R_AllocTexturePool();
1988 R_InitParticleTexture ();
1989 CL_Particles_LoadEffectInfo();
1992 static void r_part_shutdown(void)
1994 R_FreeTexturePool(&particletexturepool);
1997 static void r_part_newmap(void)
1999 CL_Particles_LoadEffectInfo();
2002 #define BATCHSIZE 256
2003 unsigned short particle_elements[BATCHSIZE*6];
2005 void R_Particles_Init (void)
2008 for (i = 0;i < BATCHSIZE;i++)
2010 particle_elements[i*6+0] = i*4+0;
2011 particle_elements[i*6+1] = i*4+1;
2012 particle_elements[i*6+2] = i*4+2;
2013 particle_elements[i*6+3] = i*4+0;
2014 particle_elements[i*6+4] = i*4+2;
2015 particle_elements[i*6+5] = i*4+3;
2018 Cvar_RegisterVariable(&r_drawparticles);
2019 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2020 Cvar_RegisterVariable(&r_drawdecals);
2021 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2022 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2025 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2027 int surfacelistindex;
2029 float *v3f, *t2f, *c4f;
2030 particletexture_t *tex;
2031 float right[3], up[3], size, ca;
2032 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2033 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2035 r_refdef.stats.decals += numsurfaces;
2036 R_Mesh_Matrix(&identitymatrix);
2037 R_Mesh_ResetTextureState();
2038 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2039 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2040 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2041 R_SetupGenericShader(true);
2042 GL_DepthMask(false);
2043 GL_DepthRange(0, 1);
2044 GL_PolygonOffset(0, 0);
2046 GL_CullFace(GL_NONE);
2048 // generate all the vertices at once
2049 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2051 d = cl.decals + surfacelist[surfacelistindex];
2054 c4f = particle_color4f + 16*surfacelistindex;
2055 ca = d->alpha * alphascale;
2056 if (r_refdef.fogenabled)
2057 ca *= FogPoint_World(d->org);
2058 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2059 Vector4Copy(c4f, c4f + 4);
2060 Vector4Copy(c4f, c4f + 8);
2061 Vector4Copy(c4f, c4f + 12);
2063 // calculate vertex positions
2064 size = d->size * cl_particles_size.value;
2065 VectorVectors(d->normal, right, up);
2066 VectorScale(right, size, right);
2067 VectorScale(up, size, up);
2068 v3f = particle_vertex3f + 12*surfacelistindex;
2069 v3f[ 0] = d->org[0] - right[0] - up[0];
2070 v3f[ 1] = d->org[1] - right[1] - up[1];
2071 v3f[ 2] = d->org[2] - right[2] - up[2];
2072 v3f[ 3] = d->org[0] - right[0] + up[0];
2073 v3f[ 4] = d->org[1] - right[1] + up[1];
2074 v3f[ 5] = d->org[2] - right[2] + up[2];
2075 v3f[ 6] = d->org[0] + right[0] + up[0];
2076 v3f[ 7] = d->org[1] + right[1] + up[1];
2077 v3f[ 8] = d->org[2] + right[2] + up[2];
2078 v3f[ 9] = d->org[0] + right[0] - up[0];
2079 v3f[10] = d->org[1] + right[1] - up[1];
2080 v3f[11] = d->org[2] + right[2] - up[2];
2082 // calculate texcoords
2083 tex = &particletexture[d->texnum];
2084 t2f = particle_texcoord2f + 8*surfacelistindex;
2085 t2f[0] = tex->s1;t2f[1] = tex->t2;
2086 t2f[2] = tex->s1;t2f[3] = tex->t1;
2087 t2f[4] = tex->s2;t2f[5] = tex->t1;
2088 t2f[6] = tex->s2;t2f[7] = tex->t2;
2091 // now render the decals all at once
2092 // (this assumes they all use one particle font texture!)
2093 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2094 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2095 GL_LockArrays(0, numsurfaces*4);
2096 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2097 GL_LockArrays(0, 0);
2100 void R_DrawDecals (void)
2108 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2109 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2111 // LordHavoc: early out conditions
2112 if ((!cl.num_decals) || (!r_drawdecals.integer))
2115 decalfade = frametime * 256 / cl_decals_fadetime.value;
2116 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2117 drawdist2 = drawdist2*drawdist2;
2119 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2121 if (!decal->typeindex)
2124 if (cl.time > decal->time2 + cl_decals_time.value)
2126 decal->alpha -= decalfade;
2127 if (decal->alpha <= 0)
2133 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2135 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2136 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2142 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2145 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2146 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2149 decal->typeindex = 0;
2150 if (cl.free_decal > i)
2154 // reduce cl.num_decals if possible
2155 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2158 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2160 decal_t *olddecals = cl.decals;
2161 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2162 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2163 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2164 Mem_Free(olddecals);
2168 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2170 int surfacelistindex;
2171 int batchstart, batchcount;
2172 const particle_t *p;
2174 rtexture_t *texture;
2175 float *v3f, *t2f, *c4f;
2176 particletexture_t *tex;
2177 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2178 float ambient[3], diffuse[3], diffusenormal[3];
2179 vec4_t colormultiplier;
2180 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2182 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2184 r_refdef.stats.particles += numsurfaces;
2185 R_Mesh_Matrix(&identitymatrix);
2186 R_Mesh_ResetTextureState();
2187 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2188 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2189 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2190 R_SetupGenericShader(true);
2191 GL_DepthMask(false);
2192 GL_DepthRange(0, 1);
2193 GL_PolygonOffset(0, 0);
2195 GL_CullFace(GL_NONE);
2197 // first generate all the vertices at once
2198 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2200 p = cl.particles + surfacelist[surfacelistindex];
2202 blendmode = p->blendmode;
2204 c4f[0] = p->color[0] * colormultiplier[0];
2205 c4f[1] = p->color[1] * colormultiplier[1];
2206 c4f[2] = p->color[2] * colormultiplier[2];
2207 c4f[3] = p->alpha * colormultiplier[3];
2210 case PBLEND_INVALID:
2213 // additive and modulate can just fade out in fog (this is correct)
2214 if (r_refdef.fogenabled)
2215 c4f[3] *= FogPoint_World(p->org);
2216 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2223 // note: lighting is not cheap!
2224 if (particletype[p->typeindex].lighting)
2226 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2227 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2228 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2229 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2231 // mix in the fog color
2232 if (r_refdef.fogenabled)
2234 fog = FogPoint_World(p->org);
2236 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2237 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2238 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2242 // copy the color into the other three vertices
2243 Vector4Copy(c4f, c4f + 4);
2244 Vector4Copy(c4f, c4f + 8);
2245 Vector4Copy(c4f, c4f + 12);
2247 size = p->size * cl_particles_size.value;
2248 tex = &particletexture[p->texnum];
2249 switch(p->orientation)
2251 case PARTICLE_INVALID:
2252 case PARTICLE_BILLBOARD:
2253 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2254 VectorScale(r_refdef.view.up, size, up);
2255 v3f[ 0] = p->org[0] - right[0] - up[0];
2256 v3f[ 1] = p->org[1] - right[1] - up[1];
2257 v3f[ 2] = p->org[2] - right[2] - up[2];
2258 v3f[ 3] = p->org[0] - right[0] + up[0];
2259 v3f[ 4] = p->org[1] - right[1] + up[1];
2260 v3f[ 5] = p->org[2] - right[2] + up[2];
2261 v3f[ 6] = p->org[0] + right[0] + up[0];
2262 v3f[ 7] = p->org[1] + right[1] + up[1];
2263 v3f[ 8] = p->org[2] + right[2] + up[2];
2264 v3f[ 9] = p->org[0] + right[0] - up[0];
2265 v3f[10] = p->org[1] + right[1] - up[1];
2266 v3f[11] = p->org[2] + right[2] - up[2];
2267 t2f[0] = tex->s1;t2f[1] = tex->t2;
2268 t2f[2] = tex->s1;t2f[3] = tex->t1;
2269 t2f[4] = tex->s2;t2f[5] = tex->t1;
2270 t2f[6] = tex->s2;t2f[7] = tex->t2;
2272 case PARTICLE_ORIENTED_DOUBLESIDED:
2273 VectorVectors(p->vel, right, up);
2274 VectorScale(right, size * p->stretch, right);
2275 VectorScale(up, size, up);
2276 v3f[ 0] = p->org[0] - right[0] - up[0];
2277 v3f[ 1] = p->org[1] - right[1] - up[1];
2278 v3f[ 2] = p->org[2] - right[2] - up[2];
2279 v3f[ 3] = p->org[0] - right[0] + up[0];
2280 v3f[ 4] = p->org[1] - right[1] + up[1];
2281 v3f[ 5] = p->org[2] - right[2] + up[2];
2282 v3f[ 6] = p->org[0] + right[0] + up[0];
2283 v3f[ 7] = p->org[1] + right[1] + up[1];
2284 v3f[ 8] = p->org[2] + right[2] + up[2];
2285 v3f[ 9] = p->org[0] + right[0] - up[0];
2286 v3f[10] = p->org[1] + right[1] - up[1];
2287 v3f[11] = p->org[2] + right[2] - up[2];
2288 t2f[0] = tex->s1;t2f[1] = tex->t2;
2289 t2f[2] = tex->s1;t2f[3] = tex->t1;
2290 t2f[4] = tex->s2;t2f[5] = tex->t1;
2291 t2f[6] = tex->s2;t2f[7] = tex->t2;
2293 case PARTICLE_SPARK:
2294 len = VectorLength(p->vel);
2295 VectorNormalize2(p->vel, up);
2296 lenfactor = p->stretch * 0.04 * len;
2297 if(lenfactor < size * 0.5)
2298 lenfactor = size * 0.5;
2299 VectorMA(p->org, -lenfactor, up, v);
2300 VectorMA(p->org, lenfactor, up, up2);
2301 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2302 t2f[0] = tex->s1;t2f[1] = tex->t2;
2303 t2f[2] = tex->s1;t2f[3] = tex->t1;
2304 t2f[4] = tex->s2;t2f[5] = tex->t1;
2305 t2f[6] = tex->s2;t2f[7] = tex->t2;
2308 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2309 VectorSubtract(p->vel, p->org, up);
2310 VectorNormalize(up);
2311 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2312 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2313 t2f[0] = 1;t2f[1] = v[0];
2314 t2f[2] = 0;t2f[3] = v[0];
2315 t2f[4] = 0;t2f[5] = v[1];
2316 t2f[6] = 1;t2f[7] = v[1];
2321 // now render batches of particles based on blendmode and texture
2322 blendmode = PBLEND_INVALID;
2324 GL_LockArrays(0, numsurfaces*4);
2327 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2329 p = cl.particles + surfacelist[surfacelistindex];
2331 if (blendmode != p->blendmode)
2333 blendmode = p->blendmode;
2337 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2339 case PBLEND_INVALID:
2341 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2344 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2348 if (texture != particletexture[p->texnum].texture)
2350 texture = particletexture[p->texnum].texture;
2351 R_Mesh_TexBind(0, R_GetTexture(texture));
2354 // iterate until we find a change in settings
2355 batchstart = surfacelistindex++;
2356 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2358 p = cl.particles + surfacelist[surfacelistindex];
2359 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2363 batchcount = surfacelistindex - batchstart;
2364 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2366 GL_LockArrays(0, 0);
2369 void R_DrawParticles (void)
2372 float minparticledist;
2374 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2380 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2381 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2383 // LordHavoc: early out conditions
2384 if ((!cl.num_particles) || (!r_drawparticles.integer))
2387 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2388 gravity = frametime * cl.movevars_gravity;
2389 dvel = 1+4*frametime;
2390 decalfade = frametime * 255 / cl_decals_fadetime.value;
2391 update = frametime > 0;
2392 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2393 drawdist2 = drawdist2*drawdist2;
2395 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2399 if (cl.free_particle > i)
2400 cl.free_particle = i;
2406 if (p->delayedspawn > cl.time)
2408 p->delayedspawn = 0;
2412 p->size += p->sizeincrease * frametime;
2413 p->alpha -= p->alphafade * frametime;
2415 if (p->alpha <= 0 || p->die <= cl.time)
2418 if (p->orientation != PARTICLE_BEAM && frametime > 0)
2420 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2422 if (p->typeindex == pt_blood)
2423 p->size += frametime * 8;
2425 p->vel[2] -= p->gravity * gravity;
2426 f = 1.0f - min(p->liquidfriction * frametime, 1);
2427 VectorScale(p->vel, f, p->vel);
2431 p->vel[2] -= p->gravity * gravity;
2434 f = 1.0f - min(p->airfriction * frametime, 1);
2435 VectorScale(p->vel, f, p->vel);
2439 VectorCopy(p->org, oldorg);
2440 VectorMA(p->org, frametime, p->vel, p->org);
2441 if (p->bounce && cl.time >= p->delayedcollisions)
2443 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2444 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2445 // or if the trace hit something flagged as NOIMPACT
2446 // then remove the particle
2447 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2449 VectorCopy(trace.endpos, p->org);
2450 // react if the particle hit something
2451 if (trace.fraction < 1)
2453 VectorCopy(trace.endpos, p->org);
2455 if (p->staintexnum >= 0)
2457 // blood - splash on solid
2458 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2461 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2462 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2463 // FIXME does the stain color need to be inverted?
2464 // it currently IS inverted as decals need that, but does R_Stain need it too?
2465 if (cl_decals.integer)
2467 // create a decal for the blood splat
2468 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->staincolor, p->staincolor, p->staintexnum, p->size * 2, p->alpha);
2473 if (p->typeindex == pt_blood)
2475 // blood - splash on solid
2476 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2478 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2480 R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2481 if (cl_decals.integer)
2483 // create a decal for the blood splat
2484 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2489 else if (p->bounce < 0)
2491 // bounce -1 means remove on impact
2496 // anything else - bounce off solid
2497 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2498 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2499 if (DotProduct(p->vel, p->vel) < 0.03)
2500 VectorClear(p->vel);
2506 if (p->typeindex != pt_static)
2508 switch (p->typeindex)
2510 case pt_entityparticle:
2511 // particle that removes itself after one rendered frame
2518 a = CL_PointSuperContents(p->org);
2519 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2523 a = CL_PointSuperContents(p->org);
2524 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2528 a = CL_PointSuperContents(p->org);
2529 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2533 if (cl.time > p->time2)
2536 p->time2 = cl.time + (rand() & 3) * 0.1;
2537 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2538 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2540 a = CL_PointSuperContents(p->org);
2541 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2549 else if (p->delayedspawn)
2552 // don't render particles too close to the view (they chew fillrate)
2553 // also don't render particles behind the view (useless)
2554 // further checks to cull to the frustum would be too slow here
2555 switch(p->typeindex)
2558 // beams have no culling
2559 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2562 if(cl_particles_visculling.integer)
2563 if (!r_refdef.viewcache.world_novis)
2564 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2566 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2568 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2571 // anything else just has to be in front of the viewer and visible at this distance
2572 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2573 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2580 if (cl.free_particle > i)
2581 cl.free_particle = i;
2584 // reduce cl.num_particles if possible
2585 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2588 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2590 particle_t *oldparticles = cl.particles;
2591 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2592 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2593 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2594 Mem_Free(oldparticles);