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 {0, 0, false}, // pt_dead
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_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_MOD, 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 // range of colors to choose from in hex RRGGBB (like HTML color tags),
73 // randomly interpolated at spawn
74 unsigned int color[2];
75 // a random texture is chosen in this range (note the second value is one
76 // past the last choosable, so for example 8,16 chooses any from 8 up and
78 // if start and end of the range are the same, no randomization is done
80 // range of size values randomly chosen when spawning, plus size increase over time
82 // range of alpha values randomly chosen when spawning, plus alpha fade
84 // how long the particle should live (note it is also removed if alpha drops to 0)
86 // how much gravity affects this particle (negative makes it fly up!)
88 // how much bounce the particle has when it hits a surface
89 // if negative the particle is removed on impact
91 // if in air this friction is applied
92 // if negative the particle accelerates
94 // if in liquid (water/slime/lava) this friction is applied
95 // if negative the particle accelerates
97 // these offsets are added to the values given to particleeffect(), and
98 // then an ellipsoid-shaped jitter is added as defined by these
99 // (they are the 3 radii)
100 float originoffset[3];
101 float velocityoffset[3];
102 float originjitter[3];
103 float velocityjitter[3];
104 float velocitymultiplier;
105 // an effect can also spawn a dlight
106 float lightradiusstart;
107 float lightradiusfade;
110 qboolean lightshadow;
113 particleeffectinfo_t;
115 #define MAX_PARTICLEEFFECTNAME 256
116 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
118 #define MAX_PARTICLEEFFECTINFO 4096
120 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
122 static int particlepalette[256] =
124 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
125 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
126 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
127 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
128 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
129 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
130 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
131 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
132 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
133 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
134 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
135 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
136 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
137 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
138 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
139 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
140 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
141 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
142 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
143 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
144 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
145 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
146 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
147 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
148 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
149 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
150 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
151 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
152 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
153 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
154 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
155 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
158 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
159 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
160 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
162 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
164 // texture numbers in particle font
165 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
166 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
167 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
168 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
169 static const int tex_rainsplash = 32;
170 static const int tex_particle = 63;
171 static const int tex_bubble = 62;
172 static const int tex_raindrop = 61;
173 static const int tex_beam = 60;
175 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
176 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
177 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
178 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
179 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
180 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
181 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
182 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
183 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
184 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
185 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
186 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
187 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
188 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
189 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
190 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
191 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
192 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
193 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
194 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
195 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
198 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
204 particleeffectinfo_t *info = NULL;
205 const char *text = textstart;
207 effectinfoindex = -1;
208 for (linenumber = 1;;linenumber++)
211 for (arrayindex = 0;arrayindex < 16;arrayindex++)
212 argv[arrayindex][0] = 0;
215 if (!COM_ParseToken_Simple(&text, true, false))
217 if (!strcmp(com_token, "\n"))
221 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
227 #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;}
228 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
229 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
230 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
231 #define readfloat(var) checkparms(2);var = atof(argv[1])
232 if (!strcmp(argv[0], "effect"))
237 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
239 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
242 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
244 if (particleeffectname[effectnameindex][0])
246 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
251 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
255 // if we run out of names, abort
256 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
258 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
261 info = particleeffectinfo + effectinfoindex;
262 info->effectnameindex = effectnameindex;
263 info->particletype = pt_alphastatic;
264 info->tex[0] = tex_particle;
265 info->tex[1] = tex_particle;
266 info->color[0] = 0xFFFFFF;
267 info->color[1] = 0xFFFFFF;
271 info->alpha[1] = 256;
272 info->alpha[2] = 256;
273 info->time[0] = 9999;
274 info->time[1] = 9999;
275 VectorSet(info->lightcolor, 1, 1, 1);
276 info->lightshadow = true;
277 info->lighttime = 9999;
279 else if (info == NULL)
281 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
284 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
285 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
286 else if (!strcmp(argv[0], "type"))
289 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
290 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
291 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
292 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
293 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
294 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
295 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
296 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
297 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
298 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
299 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
300 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
301 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
303 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
304 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
305 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
306 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
307 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
308 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
309 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
310 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
311 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
312 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
313 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
314 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
315 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
316 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
317 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
318 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
319 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
320 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
321 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
322 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
323 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
324 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
325 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
326 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
328 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
337 int CL_ParticleEffectIndexForName(const char *name)
340 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
341 if (!strcmp(particleeffectname[i], name))
346 const char *CL_ParticleEffectNameForIndex(int i)
348 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
350 return particleeffectname[i];
353 // MUST match effectnameindex_t in client.h
354 static const char *standardeffectnames[EFFECT_TOTAL] =
378 "TE_TEI_BIGEXPLOSION",
394 void CL_Particles_LoadEffectInfo(void)
397 unsigned char *filedata;
398 fs_offset_t filesize;
399 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
400 memset(particleeffectname, 0, sizeof(particleeffectname));
401 for (i = 0;i < EFFECT_TOTAL;i++)
402 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
403 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
406 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
416 void CL_ReadPointFile_f (void);
417 void CL_Particles_Init (void)
419 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)");
420 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
422 Cvar_RegisterVariable (&cl_particles);
423 Cvar_RegisterVariable (&cl_particles_quality);
424 Cvar_RegisterVariable (&cl_particles_alpha);
425 Cvar_RegisterVariable (&cl_particles_size);
426 Cvar_RegisterVariable (&cl_particles_quake);
427 Cvar_RegisterVariable (&cl_particles_blood);
428 Cvar_RegisterVariable (&cl_particles_blood_alpha);
429 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
430 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
431 Cvar_RegisterVariable (&cl_particles_explosions_shell);
432 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
433 Cvar_RegisterVariable (&cl_particles_rain);
434 Cvar_RegisterVariable (&cl_particles_snow);
435 Cvar_RegisterVariable (&cl_particles_smoke);
436 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
437 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
438 Cvar_RegisterVariable (&cl_particles_sparks);
439 Cvar_RegisterVariable (&cl_particles_bubbles);
440 Cvar_RegisterVariable (&cl_decals);
441 Cvar_RegisterVariable (&cl_decals_time);
442 Cvar_RegisterVariable (&cl_decals_fadetime);
445 void CL_Particles_Shutdown (void)
449 // list of all 26 parameters:
450 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
451 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
452 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
453 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
454 // palpha - opacity of particle as 0-255 (can be more than 255)
455 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
456 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
457 // pgravity - how much effect gravity has on the particle (0-1)
458 // 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
459 // px,py,pz - starting origin of particle
460 // pvx,pvy,pvz - starting velocity of particle
461 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
462 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)
467 if (!cl_particles.integer)
469 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
470 if (cl.free_particle >= cl.max_particles)
473 lifetime = palpha / min(1, palphafade);
474 part = &cl.particles[cl.free_particle++];
475 if (cl.num_particles < cl.free_particle)
476 cl.num_particles = cl.free_particle;
477 memset(part, 0, sizeof(*part));
478 part->typeindex = ptypeindex;
479 l2 = (int)lhrandom(0.5, 256.5);
481 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
482 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
483 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
486 part->sizeincrease = psizeincrease;
487 part->alpha = palpha;
488 part->alphafade = palphafade;
489 part->gravity = pgravity;
490 part->bounce = pbounce;
492 part->org[0] = px + originjitter * v[0];
493 part->org[1] = py + originjitter * v[1];
494 part->org[2] = pz + originjitter * v[2];
495 part->vel[0] = pvx + velocityjitter * v[0];
496 part->vel[1] = pvy + velocityjitter * v[1];
497 part->vel[2] = pvz + velocityjitter * v[2];
499 part->airfriction = pairfriction;
500 part->liquidfriction = pliquidfriction;
501 part->die = cl.time + lifetime;
502 part->delayedcollisions = 0;
503 part->qualityreduction = pqualityreduction;
504 if (part->typeindex == pt_blood)
505 part->gravity += 1; // FIXME: this is a legacy hack, effectinfo.txt doesn't have gravity on blood (nor do the particle calls in the engine)
506 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
507 if (part->typeindex == pt_rain)
511 float lifetime = part->die - cl.time;
514 // turn raindrop into simple spark and create delayedspawn splash effect
515 part->typeindex = pt_spark;
517 VectorMA(part->org, lifetime, part->vel, endvec);
518 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
519 part->die = cl.time + lifetime * trace.fraction;
520 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);
523 part2->delayedspawn = part->die;
524 part2->die += part->die - cl.time;
525 for (i = rand() & 7;i < 10;i++)
527 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);
530 part2->delayedspawn = part->die;
531 part2->die += part->die - cl.time;
536 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
538 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
541 VectorMA(part->org, lifetime, part->vel, endvec);
542 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
543 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
548 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
552 if (!cl_decals.integer)
554 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
555 if (cl.free_decal >= cl.max_decals)
557 decal = &cl.decals[cl.free_decal++];
558 if (cl.num_decals < cl.free_decal)
559 cl.num_decals = cl.free_decal;
560 memset(decal, 0, sizeof(*decal));
561 decal->typeindex = pt_decal;
562 decal->texnum = texnum;
563 VectorAdd(org, normal, decal->org);
564 VectorCopy(normal, decal->normal);
566 decal->alpha = alpha;
567 decal->time2 = cl.time;
568 l2 = (int)lhrandom(0.5, 256.5);
570 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
571 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
572 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
573 decal->owner = hitent;
576 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
577 decal->ownermodel = cl.entities[decal->owner].render.model;
578 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
579 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
583 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
586 float bestfrac, bestorg[3], bestnormal[3];
588 int besthitent = 0, hitent;
591 for (i = 0;i < 32;i++)
594 VectorMA(org, maxdist, org2, org2);
595 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
596 // take the closest trace result that doesn't end up hitting a NOMARKS
597 // surface (sky for example)
598 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
600 bestfrac = trace.fraction;
602 VectorCopy(trace.endpos, bestorg);
603 VectorCopy(trace.plane.normal, bestnormal);
607 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
610 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
611 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
612 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)
615 matrix4x4_t tempmatrix;
616 VectorLerp(originmins, 0.5, originmaxs, center);
617 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
618 if (effectnameindex == EFFECT_SVC_PARTICLE)
620 if (cl_particles.integer)
622 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
624 CL_ParticleExplosion(center);
625 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
626 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
629 count *= cl_particles_quality.value;
630 for (;count > 0;count--)
632 int k = particlepalette[palettecolor + (rand()&7)];
633 if (cl_particles_quake.integer)
634 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, 0);
635 else if (gamemode == GAME_GOODVSBAD2)
636 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10, true, 0);
638 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15, true, 0);
643 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
644 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
645 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
647 else if (effectnameindex == EFFECT_TE_SPIKE)
649 if (cl_particles_bulletimpacts.integer)
651 if (cl_particles_quake.integer)
653 if (cl_particles_smoke.integer)
654 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
658 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
659 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
660 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);
664 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
665 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
667 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
669 if (cl_particles_bulletimpacts.integer)
671 if (cl_particles_quake.integer)
673 if (cl_particles_smoke.integer)
674 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
678 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
679 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
680 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);
684 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
685 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
686 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);
688 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
690 if (cl_particles_bulletimpacts.integer)
692 if (cl_particles_quake.integer)
694 if (cl_particles_smoke.integer)
695 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
699 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
700 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
701 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);
705 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
706 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
708 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
710 if (cl_particles_bulletimpacts.integer)
712 if (cl_particles_quake.integer)
714 if (cl_particles_smoke.integer)
715 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
719 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
720 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
721 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);
725 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
726 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
727 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);
729 else if (effectnameindex == EFFECT_TE_BLOOD)
731 if (!cl_particles_blood.integer)
733 if (cl_particles_quake.integer)
734 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
737 static double bloodaccumulator = 0;
738 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);
739 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
740 for (;bloodaccumulator > 0;bloodaccumulator--)
741 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0);
744 else if (effectnameindex == EFFECT_TE_SPARK)
745 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
746 else if (effectnameindex == EFFECT_TE_PLASMABURN)
748 // plasma scorch mark
749 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
750 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
751 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
753 else if (effectnameindex == EFFECT_TE_GUNSHOT)
755 if (cl_particles_bulletimpacts.integer)
757 if (cl_particles_quake.integer)
758 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
761 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
762 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
763 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);
767 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
768 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
770 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
772 if (cl_particles_bulletimpacts.integer)
774 if (cl_particles_quake.integer)
775 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
778 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
779 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
780 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);
784 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
785 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
786 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);
788 else if (effectnameindex == EFFECT_TE_EXPLOSION)
790 CL_ParticleExplosion(center);
791 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);
793 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
795 CL_ParticleExplosion(center);
796 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);
798 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
800 if (cl_particles_quake.integer)
803 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
806 CL_NewParticle(pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, 0);
808 CL_NewParticle(pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, 0);
812 CL_ParticleExplosion(center);
813 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);
815 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
816 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);
817 else if (effectnameindex == EFFECT_TE_FLAMEJET)
819 count *= cl_particles_quality.value;
821 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);
823 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
825 float i, j, inc, vel;
828 inc = 8 / cl_particles_quality.value;
829 for (i = -128;i < 128;i += inc)
831 for (j = -128;j < 128;j += inc)
833 dir[0] = j + lhrandom(0, inc);
834 dir[1] = i + lhrandom(0, inc);
836 org[0] = center[0] + dir[0];
837 org[1] = center[1] + dir[1];
838 org[2] = center[2] + lhrandom(0, 64);
839 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
840 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0);
844 else if (effectnameindex == EFFECT_TE_TELEPORT)
846 float i, j, k, inc, vel;
849 inc = 8 / cl_particles_quality.value;
850 for (i = -16;i < 16;i += inc)
852 for (j = -16;j < 16;j += inc)
854 for (k = -24;k < 32;k += inc)
856 VectorSet(dir, i*8, j*8, k*8);
857 VectorNormalize(dir);
858 vel = lhrandom(50, 113);
859 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);
863 CL_NewParticle(pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0);
864 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);
866 else if (effectnameindex == EFFECT_TE_TEI_G3)
867 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);
868 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
870 if (cl_particles_smoke.integer)
872 count *= 0.25f * cl_particles_quality.value;
874 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);
877 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
879 CL_ParticleExplosion(center);
880 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);
882 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
885 if (cl_stainmaps.integer)
886 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
887 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
888 if (cl_particles_smoke.integer)
889 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
890 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);
891 if (cl_particles_sparks.integer)
892 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
893 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);
894 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);
896 else if (effectnameindex == EFFECT_EF_FLAME)
898 count *= 300 * cl_particles_quality.value;
900 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);
901 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);
903 else if (effectnameindex == EFFECT_EF_STARDUST)
905 count *= 200 * cl_particles_quality.value;
907 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);
908 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);
910 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
914 int smoke, blood, bubbles, r, color;
916 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
919 Vector4Set(light, 0, 0, 0, 0);
921 if (effectnameindex == EFFECT_TR_ROCKET)
922 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
923 else if (effectnameindex == EFFECT_TR_VORESPIKE)
925 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
926 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
928 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
930 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
931 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
935 matrix4x4_t tempmatrix;
936 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
937 R_RTLight_Update(&r_refdef.scene.lights[r_refdef.scene.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
944 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
947 VectorSubtract(originmaxs, originmins, dir);
948 len = VectorNormalizeLength(dir);
951 dec = -ent->persistent.trail_time;
952 ent->persistent.trail_time += len;
953 if (ent->persistent.trail_time < 0.01f)
956 // if we skip out, leave it reset
957 ent->persistent.trail_time = 0.0f;
962 // advance into this frame to reach the first puff location
963 VectorMA(originmins, dec, dir, pos);
966 smoke = cl_particles.integer && cl_particles_smoke.integer;
967 blood = cl_particles.integer && cl_particles_blood.integer;
968 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
969 qd = 1.0f / cl_particles_quality.value;
976 if (effectnameindex == EFFECT_TR_BLOOD)
978 if (cl_particles_quake.integer)
980 color = particlepalette[67 + (rand()&3)];
981 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
986 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0);
989 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
991 if (cl_particles_quake.integer)
994 color = particlepalette[67 + (rand()&3)];
995 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
1000 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0);
1006 if (effectnameindex == EFFECT_TR_ROCKET)
1008 if (cl_particles_quake.integer)
1011 color = particlepalette[ramp3[r]];
1012 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
1016 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);
1017 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);
1020 else if (effectnameindex == EFFECT_TR_GRENADE)
1022 if (cl_particles_quake.integer)
1025 color = particlepalette[ramp3[r]];
1026 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0);
1030 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);
1033 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1035 if (cl_particles_quake.integer)
1038 color = particlepalette[52 + (rand()&7)];
1039 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0);
1040 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0);
1042 else if (gamemode == GAME_GOODVSBAD2)
1045 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);
1049 color = particlepalette[20 + (rand()&7)];
1050 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);
1053 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1055 if (cl_particles_quake.integer)
1058 color = particlepalette[230 + (rand()&7)];
1059 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0);
1060 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0);
1064 color = particlepalette[226 + (rand()&7)];
1065 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);
1068 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1070 if (cl_particles_quake.integer)
1072 color = particlepalette[152 + (rand()&3)];
1073 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0);
1075 else if (gamemode == GAME_GOODVSBAD2)
1078 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);
1080 else if (gamemode == GAME_PRYDON)
1083 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);
1086 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);
1088 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1091 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);
1093 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1096 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);
1098 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1099 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);
1103 if (effectnameindex == EFFECT_TR_ROCKET)
1104 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0);
1105 else if (effectnameindex == EFFECT_TR_GRENADE)
1106 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0);
1108 // advance to next time and position
1111 VectorMA (pos, dec, dir, pos);
1114 ent->persistent.trail_time = len;
1116 else if (developer.integer >= 1)
1117 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1120 // this is also called on point effects with spawndlight = true and
1121 // spawnparticles = true
1122 // it is called CL_ParticleTrail because most code does not want to supply
1123 // these parameters, only trail handling does
1124 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)
1127 qboolean found = false;
1128 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1130 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1131 return; // no such effect
1133 VectorLerp(originmins, 0.5, originmaxs, center);
1134 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1136 int effectinfoindex;
1139 particleeffectinfo_t *info;
1141 vec3_t centervelocity;
1147 qboolean underwater;
1148 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1149 VectorLerp(originmins, 0.5, originmaxs, center);
1150 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1151 supercontents = CL_PointSuperContents(center);
1152 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1153 VectorSubtract(originmaxs, originmins, traildir);
1154 traillen = VectorLength(traildir);
1155 VectorNormalize(traildir);
1156 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1158 if (info->effectnameindex == effectnameindex)
1161 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1163 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1166 // spawn a dlight if requested
1167 if (info->lightradiusstart > 0 && spawndlight)
1169 matrix4x4_t tempmatrix;
1170 if (info->trailspacing > 0)
1171 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1173 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1174 if (info->lighttime > 0 && info->lightradiusfade > 0)
1176 // light flash (explosion, etc)
1177 // called when effect starts
1178 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);
1183 // called by CL_LinkNetworkEntity
1184 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1185 R_RTLight_Update(&r_refdef.scene.lights[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);
1189 if (!spawnparticles)
1194 if (info->tex[1] > info->tex[0])
1196 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1197 tex = min(tex, info->tex[1] - 1);
1199 if (info->particletype == pt_decal)
1200 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]);
1201 else if (info->particletype == pt_beam)
1202 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, 0);
1205 if (!cl_particles.integer)
1207 switch (info->particletype)
1209 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1210 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1211 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1212 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1213 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1214 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1217 VectorCopy(originmins, trailpos);
1218 if (info->trailspacing > 0)
1220 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1221 trailstep = info->trailspacing / cl_particles_quality.value;
1225 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1228 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1229 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1231 if (info->tex[1] > info->tex[0])
1233 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1234 tex = min(tex, info->tex[1] - 1);
1238 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1239 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1240 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1243 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, 0);
1245 VectorMA(trailpos, trailstep, traildir, trailpos);
1252 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1255 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)
1257 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1265 void CL_EntityParticles (const entity_t *ent)
1268 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1269 static vec3_t avelocities[NUMVERTEXNORMALS];
1270 if (!cl_particles.integer) return;
1271 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1273 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1275 if (!avelocities[0][0])
1276 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1277 avelocities[0][i] = lhrandom(0, 2.55);
1279 for (i = 0;i < NUMVERTEXNORMALS;i++)
1281 yaw = cl.time * avelocities[i][0];
1282 pitch = cl.time * avelocities[i][1];
1283 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1284 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1285 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1286 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);
1291 void CL_ReadPointFile_f (void)
1293 vec3_t org, leakorg;
1295 char *pointfile = NULL, *pointfilepos, *t, tchar;
1296 char name[MAX_OSPATH];
1301 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1302 strlcat (name, ".pts", sizeof (name));
1303 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1306 Con_Printf("Could not open %s\n", name);
1310 Con_Printf("Reading %s...\n", name);
1311 VectorClear(leakorg);
1314 pointfilepos = pointfile;
1315 while (*pointfilepos)
1317 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1322 while (*t && *t != '\n' && *t != '\r')
1326 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1332 VectorCopy(org, leakorg);
1335 if (cl.num_particles < cl.max_particles - 3)
1338 CL_NewParticle(pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30);
1341 Mem_Free(pointfile);
1342 VectorCopy(leakorg, org);
1343 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1345 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);
1346 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);
1347 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);
1352 CL_ParseParticleEffect
1354 Parse an effect out of the server message
1357 void CL_ParseParticleEffect (void)
1360 int i, count, msgcount, color;
1362 MSG_ReadVector(org, cls.protocol);
1363 for (i=0 ; i<3 ; i++)
1364 dir[i] = MSG_ReadChar ();
1365 msgcount = MSG_ReadByte ();
1366 color = MSG_ReadByte ();
1368 if (msgcount == 255)
1373 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1378 CL_ParticleExplosion
1382 void CL_ParticleExplosion (const vec3_t org)
1388 if (cl_stainmaps.integer)
1389 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1390 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1392 if (cl_particles_quake.integer)
1394 for (i = 0;i < 1024;i++)
1400 color = particlepalette[ramp1[r]];
1401 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0);
1405 color = particlepalette[ramp2[r]];
1406 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0);
1412 i = CL_PointSuperContents(org);
1413 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1415 if (cl_particles.integer && cl_particles_bubbles.integer)
1416 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1417 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);
1421 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1423 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1427 for (k = 0;k < 16;k++)
1430 VectorMA(org, 128, v2, v);
1431 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1432 if (trace.fraction >= 0.1)
1435 VectorSubtract(trace.endpos, org, v2);
1436 VectorScale(v2, 2.0f, v2);
1437 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);
1443 if (cl_particles_explosions_shell.integer)
1444 R_NewExplosion(org);
1449 CL_ParticleExplosion2
1453 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1456 if (!cl_particles.integer) return;
1458 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1460 k = particlepalette[colorStart + (i % colorLength)];
1461 if (cl_particles_quake.integer)
1462 CL_NewParticle(pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256, true, 0);
1464 CL_NewParticle(pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0);
1468 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1470 if (cl_particles_sparks.integer)
1472 sparkcount *= cl_particles_quality.value;
1473 while(sparkcount-- > 0)
1474 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);
1478 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1480 if (cl_particles_smoke.integer)
1482 smokecount *= cl_particles_quality.value;
1483 while(smokecount-- > 0)
1484 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);
1488 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)
1491 if (!cl_particles.integer) return;
1493 count = (int)(count * cl_particles_quality.value);
1496 k = particlepalette[colorbase + (rand()&3)];
1497 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);
1501 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1504 float minz, maxz, lifetime = 30;
1505 if (!cl_particles.integer) return;
1506 if (dir[2] < 0) // falling
1508 minz = maxs[2] + dir[2] * 0.1;
1511 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1516 maxz = maxs[2] + dir[2] * 0.1;
1518 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1521 count = (int)(count * cl_particles_quality.value);
1526 if (!cl_particles_rain.integer) break;
1527 count *= 4; // ick, this should be in the mod or maps?
1531 k = particlepalette[colorbase + (rand()&3)];
1532 if (gamemode == GAME_GOODVSBAD2)
1533 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);
1535 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);
1539 if (!cl_particles_snow.integer) break;
1542 k = particlepalette[colorbase + (rand()&3)];
1543 if (gamemode == GAME_GOODVSBAD2)
1544 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);
1546 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);
1550 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1554 #define MAX_PARTICLETEXTURES 64
1555 // particletexture_t is a rectangle in the particlefonttexture
1556 typedef struct particletexture_s
1558 rtexture_t *texture;
1559 float s1, t1, s2, t2;
1563 static rtexturepool_t *particletexturepool;
1564 static rtexture_t *particlefonttexture;
1565 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1567 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1568 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1569 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1570 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1572 #define PARTICLETEXTURESIZE 64
1573 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1575 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1579 dz = 1 - (dx*dx+dy*dy);
1580 if (dz > 0) // it does hit the sphere
1584 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1585 VectorNormalize(normal);
1586 dot = DotProduct(normal, light);
1587 if (dot > 0.5) // interior reflection
1588 f += ((dot * 2) - 1);
1589 else if (dot < -0.5) // exterior reflection
1590 f += ((dot * -2) - 1);
1592 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1593 VectorNormalize(normal);
1594 dot = DotProduct(normal, light);
1595 if (dot > 0.5) // interior reflection
1596 f += ((dot * 2) - 1);
1597 else if (dot < -0.5) // exterior reflection
1598 f += ((dot * -2) - 1);
1600 f += 16; // just to give it a haze so you can see the outline
1601 f = bound(0, f, 255);
1602 return (unsigned char) f;
1608 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1610 int basex, basey, y;
1611 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1612 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1613 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1614 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1617 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1620 float cx, cy, dx, dy, f, iradius;
1622 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1623 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1624 iradius = 1.0f / radius;
1625 alpha *= (1.0f / 255.0f);
1626 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1628 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1632 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1637 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1638 d[0] += (int)(f * (blue - d[0]));
1639 d[1] += (int)(f * (green - d[1]));
1640 d[2] += (int)(f * (red - d[2]));
1646 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1649 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1651 data[0] = bound(minb, data[0], maxb);
1652 data[1] = bound(ming, data[1], maxg);
1653 data[2] = bound(minr, data[2], maxr);
1657 void particletextureinvert(unsigned char *data)
1660 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1662 data[0] = 255 - data[0];
1663 data[1] = 255 - data[1];
1664 data[2] = 255 - data[2];
1668 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1669 static void R_InitBloodTextures (unsigned char *particletexturedata)
1672 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1675 for (i = 0;i < 8;i++)
1677 memset(&data[0][0][0], 255, sizeof(data));
1678 for (k = 0;k < 24;k++)
1679 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1680 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1681 particletextureinvert(&data[0][0][0]);
1682 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1686 for (i = 0;i < 8;i++)
1688 memset(&data[0][0][0], 255, sizeof(data));
1690 for (j = 1;j < 10;j++)
1691 for (k = min(j, m - 1);k < m;k++)
1692 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1693 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1694 particletextureinvert(&data[0][0][0]);
1695 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1700 //uncomment this to make engine save out particle font to a tga file when run
1701 //#define DUMPPARTICLEFONT
1703 static void R_InitParticleTexture (void)
1705 int x, y, d, i, k, m;
1709 // a note: decals need to modulate (multiply) the background color to
1710 // properly darken it (stain), and they need to be able to alpha fade,
1711 // this is a very difficult challenge because it means fading to white
1712 // (no change to background) rather than black (darkening everything
1713 // behind the whole decal polygon), and to accomplish this the texture is
1714 // inverted (dark red blood on white background becomes brilliant cyan
1715 // and white on black background) so we can alpha fade it to black, then
1716 // we invert it again during the blendfunc to make it work...
1718 #ifndef DUMPPARTICLEFONT
1719 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1720 if (!particlefonttexture)
1723 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1724 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1725 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1728 for (i = 0;i < 8;i++)
1730 memset(&data[0][0][0], 255, sizeof(data));
1733 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1735 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1736 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1738 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1740 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1741 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1743 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1744 d = (noise2[y][x] - 128) * 3 + 192;
1746 d = (int)(d * (1-(dx*dx+dy*dy)));
1747 d = (d * noise1[y][x]) >> 7;
1748 d = bound(0, d, 255);
1749 data[y][x][3] = (unsigned char) d;
1756 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1760 memset(&data[0][0][0], 255, sizeof(data));
1761 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1763 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1764 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1766 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1767 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1768 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1771 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1774 memset(&data[0][0][0], 255, sizeof(data));
1775 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1777 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1778 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1780 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1781 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1782 d = bound(0, d, 255);
1783 data[y][x][3] = (unsigned char) d;
1786 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1789 memset(&data[0][0][0], 255, sizeof(data));
1790 light[0] = 1;light[1] = 1;light[2] = 1;
1791 VectorNormalize(light);
1792 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1794 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1795 // stretch upper half of bubble by +50% and shrink lower half by -50%
1796 // (this gives an elongated teardrop shape)
1798 dy = (dy - 0.5f) * 2.0f;
1800 dy = (dy - 0.5f) / 1.5f;
1801 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1803 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1804 // shrink bubble width to half
1806 data[y][x][3] = shadebubble(dx, dy, light);
1809 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1812 memset(&data[0][0][0], 255, sizeof(data));
1813 light[0] = 1;light[1] = 1;light[2] = 1;
1814 VectorNormalize(light);
1815 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1817 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1818 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1820 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1821 data[y][x][3] = shadebubble(dx, dy, light);
1824 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1826 // Blood particles and blood decals
1827 R_InitBloodTextures (particletexturedata);
1830 for (i = 0;i < 8;i++)
1832 memset(&data[0][0][0], 255, sizeof(data));
1833 for (k = 0;k < 12;k++)
1834 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1835 for (k = 0;k < 3;k++)
1836 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1837 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1838 particletextureinvert(&data[0][0][0]);
1839 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1842 #ifdef DUMPPARTICLEFONT
1843 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1846 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1848 Mem_Free(particletexturedata);
1850 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1852 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1853 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1854 particletexture[i].texture = particlefonttexture;
1855 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1856 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1857 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1858 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1861 #ifndef DUMPPARTICLEFONT
1862 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1863 if (!particletexture[tex_beam].texture)
1866 unsigned char noise3[64][64], data2[64][16][4];
1868 fractalnoise(&noise3[0][0], 64, 4);
1870 for (y = 0;y < 64;y++)
1872 dy = (y - 0.5f*64) / (64*0.5f-1);
1873 for (x = 0;x < 16;x++)
1875 dx = (x - 0.5f*16) / (16*0.5f-2);
1876 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1877 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1878 data2[y][x][3] = 255;
1882 #ifdef DUMPPARTICLEFONT
1883 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1885 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1887 particletexture[tex_beam].s1 = 0;
1888 particletexture[tex_beam].t1 = 0;
1889 particletexture[tex_beam].s2 = 1;
1890 particletexture[tex_beam].t2 = 1;
1893 static void r_part_start(void)
1895 particletexturepool = R_AllocTexturePool();
1896 R_InitParticleTexture ();
1897 CL_Particles_LoadEffectInfo();
1900 static void r_part_shutdown(void)
1902 R_FreeTexturePool(&particletexturepool);
1905 static void r_part_newmap(void)
1909 #define BATCHSIZE 256
1910 int particle_element3i[BATCHSIZE*6];
1912 void R_Particles_Init (void)
1915 for (i = 0;i < BATCHSIZE;i++)
1917 particle_element3i[i*6+0] = i*4+0;
1918 particle_element3i[i*6+1] = i*4+1;
1919 particle_element3i[i*6+2] = i*4+2;
1920 particle_element3i[i*6+3] = i*4+0;
1921 particle_element3i[i*6+4] = i*4+2;
1922 particle_element3i[i*6+5] = i*4+3;
1925 Cvar_RegisterVariable(&r_drawparticles);
1926 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
1927 Cvar_RegisterVariable(&r_drawdecals);
1928 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
1929 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1932 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1934 int surfacelistindex;
1936 float *v3f, *t2f, *c4f;
1937 particletexture_t *tex;
1938 float right[3], up[3], size, ca;
1939 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1940 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1942 r_refdef.stats.decals += numsurfaces;
1943 R_Mesh_Matrix(&identitymatrix);
1944 R_Mesh_ResetTextureState();
1945 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1946 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1947 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1948 R_SetupGenericShader(true);
1949 GL_DepthMask(false);
1950 GL_DepthRange(0, 1);
1951 GL_PolygonOffset(0, 0);
1953 GL_CullFace(GL_NONE);
1955 // generate all the vertices at once
1956 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1958 d = cl.decals + surfacelist[surfacelistindex];
1961 c4f = particle_color4f + 16*surfacelistindex;
1962 ca = d->alpha * alphascale;
1963 if (r_refdef.fogenabled)
1964 ca *= FogPoint_World(d->org);
1965 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1966 Vector4Copy(c4f, c4f + 4);
1967 Vector4Copy(c4f, c4f + 8);
1968 Vector4Copy(c4f, c4f + 12);
1970 // calculate vertex positions
1971 size = d->size * cl_particles_size.value;
1972 VectorVectors(d->normal, right, up);
1973 VectorScale(right, size, right);
1974 VectorScale(up, size, up);
1975 v3f = particle_vertex3f + 12*surfacelistindex;
1976 v3f[ 0] = d->org[0] - right[0] - up[0];
1977 v3f[ 1] = d->org[1] - right[1] - up[1];
1978 v3f[ 2] = d->org[2] - right[2] - up[2];
1979 v3f[ 3] = d->org[0] - right[0] + up[0];
1980 v3f[ 4] = d->org[1] - right[1] + up[1];
1981 v3f[ 5] = d->org[2] - right[2] + up[2];
1982 v3f[ 6] = d->org[0] + right[0] + up[0];
1983 v3f[ 7] = d->org[1] + right[1] + up[1];
1984 v3f[ 8] = d->org[2] + right[2] + up[2];
1985 v3f[ 9] = d->org[0] + right[0] - up[0];
1986 v3f[10] = d->org[1] + right[1] - up[1];
1987 v3f[11] = d->org[2] + right[2] - up[2];
1989 // calculate texcoords
1990 tex = &particletexture[d->texnum];
1991 t2f = particle_texcoord2f + 8*surfacelistindex;
1992 t2f[0] = tex->s1;t2f[1] = tex->t2;
1993 t2f[2] = tex->s1;t2f[3] = tex->t1;
1994 t2f[4] = tex->s2;t2f[5] = tex->t1;
1995 t2f[6] = tex->s2;t2f[7] = tex->t2;
1998 // now render the decals all at once
1999 // (this assumes they all use one particle font texture!)
2000 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2001 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2002 GL_LockArrays(0, numsurfaces*4);
2003 R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
2004 GL_LockArrays(0, 0);
2007 void R_DrawDecals (void)
2015 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2016 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2018 // LordHavoc: early out conditions
2019 if ((!cl.num_decals) || (!r_drawdecals.integer))
2022 decalfade = frametime * 256 / cl_decals_fadetime.value;
2023 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2024 drawdist2 = drawdist2*drawdist2;
2026 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2028 if (!decal->typeindex)
2031 if (cl.time > decal->time2 + cl_decals_time.value)
2033 decal->alpha -= decalfade;
2034 if (decal->alpha <= 0)
2040 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2042 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2043 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2049 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))
2050 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2053 decal->typeindex = 0;
2054 if (cl.free_decal > i)
2058 // reduce cl.num_decals if possible
2059 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2062 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2064 decal_t *olddecals = cl.decals;
2065 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2066 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2067 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2068 Mem_Free(olddecals);
2072 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2074 int surfacelistindex;
2075 int batchstart, batchcount;
2076 const particle_t *p;
2078 rtexture_t *texture;
2079 float *v3f, *t2f, *c4f;
2080 particletexture_t *tex;
2081 float up2[3], v[3], right[3], up[3], fog, ifog, size;
2082 float ambient[3], diffuse[3], diffusenormal[3];
2083 vec4_t colormultiplier;
2084 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2086 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));
2088 r_refdef.stats.particles += numsurfaces;
2089 R_Mesh_Matrix(&identitymatrix);
2090 R_Mesh_ResetTextureState();
2091 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2092 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2093 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2094 R_SetupGenericShader(true);
2095 GL_DepthMask(false);
2096 GL_DepthRange(0, 1);
2097 GL_PolygonOffset(0, 0);
2099 GL_CullFace(GL_NONE);
2101 // first generate all the vertices at once
2102 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2104 p = cl.particles + surfacelist[surfacelistindex];
2106 blendmode = particletype[p->typeindex].blendmode;
2108 c4f[0] = p->color[0] * colormultiplier[0];
2109 c4f[1] = p->color[1] * colormultiplier[1];
2110 c4f[2] = p->color[2] * colormultiplier[2];
2111 c4f[3] = p->alpha * colormultiplier[3];
2116 // additive and modulate can just fade out in fog (this is correct)
2117 if (r_refdef.fogenabled)
2118 c4f[3] *= FogPoint_World(p->org);
2119 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2126 // note: lighting is not cheap!
2127 if (particletype[p->typeindex].lighting)
2129 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2130 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2131 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2132 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2134 // mix in the fog color
2135 if (r_refdef.fogenabled)
2137 fog = FogPoint_World(p->org);
2139 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2140 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2141 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2145 // copy the color into the other three vertices
2146 Vector4Copy(c4f, c4f + 4);
2147 Vector4Copy(c4f, c4f + 8);
2148 Vector4Copy(c4f, c4f + 12);
2150 size = p->size * cl_particles_size.value;
2151 tex = &particletexture[p->texnum];
2152 switch(particletype[p->typeindex].orientation)
2154 case PARTICLE_BILLBOARD:
2155 VectorScale(r_refdef.view.left, -size, right);
2156 VectorScale(r_refdef.view.up, size, up);
2157 v3f[ 0] = p->org[0] - right[0] - up[0];
2158 v3f[ 1] = p->org[1] - right[1] - up[1];
2159 v3f[ 2] = p->org[2] - right[2] - up[2];
2160 v3f[ 3] = p->org[0] - right[0] + up[0];
2161 v3f[ 4] = p->org[1] - right[1] + up[1];
2162 v3f[ 5] = p->org[2] - right[2] + up[2];
2163 v3f[ 6] = p->org[0] + right[0] + up[0];
2164 v3f[ 7] = p->org[1] + right[1] + up[1];
2165 v3f[ 8] = p->org[2] + right[2] + up[2];
2166 v3f[ 9] = p->org[0] + right[0] - up[0];
2167 v3f[10] = p->org[1] + right[1] - up[1];
2168 v3f[11] = p->org[2] + right[2] - up[2];
2169 t2f[0] = tex->s1;t2f[1] = tex->t2;
2170 t2f[2] = tex->s1;t2f[3] = tex->t1;
2171 t2f[4] = tex->s2;t2f[5] = tex->t1;
2172 t2f[6] = tex->s2;t2f[7] = tex->t2;
2174 case PARTICLE_ORIENTED_DOUBLESIDED:
2175 VectorVectors(p->vel, right, up);
2176 VectorScale(right, size, right);
2177 VectorScale(up, size, up);
2178 v3f[ 0] = p->org[0] - right[0] - up[0];
2179 v3f[ 1] = p->org[1] - right[1] - up[1];
2180 v3f[ 2] = p->org[2] - right[2] - up[2];
2181 v3f[ 3] = p->org[0] - right[0] + up[0];
2182 v3f[ 4] = p->org[1] - right[1] + up[1];
2183 v3f[ 5] = p->org[2] - right[2] + up[2];
2184 v3f[ 6] = p->org[0] + right[0] + up[0];
2185 v3f[ 7] = p->org[1] + right[1] + up[1];
2186 v3f[ 8] = p->org[2] + right[2] + up[2];
2187 v3f[ 9] = p->org[0] + right[0] - up[0];
2188 v3f[10] = p->org[1] + right[1] - up[1];
2189 v3f[11] = p->org[2] + right[2] - up[2];
2190 t2f[0] = tex->s1;t2f[1] = tex->t2;
2191 t2f[2] = tex->s1;t2f[3] = tex->t1;
2192 t2f[4] = tex->s2;t2f[5] = tex->t1;
2193 t2f[6] = tex->s2;t2f[7] = tex->t2;
2195 case PARTICLE_SPARK:
2196 VectorMA(p->org, -0.04, p->vel, v);
2197 VectorMA(p->org, 0.04, p->vel, up2);
2198 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2199 t2f[0] = tex->s1;t2f[1] = tex->t2;
2200 t2f[2] = tex->s1;t2f[3] = tex->t1;
2201 t2f[4] = tex->s2;t2f[5] = tex->t1;
2202 t2f[6] = tex->s2;t2f[7] = tex->t2;
2205 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2206 VectorSubtract(p->vel, p->org, up);
2207 VectorNormalize(up);
2208 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2209 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2210 t2f[0] = 1;t2f[1] = v[0];
2211 t2f[2] = 0;t2f[3] = v[0];
2212 t2f[4] = 0;t2f[5] = v[1];
2213 t2f[6] = 1;t2f[7] = v[1];
2218 // now render batches of particles based on blendmode and texture
2221 GL_LockArrays(0, numsurfaces*4);
2224 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2226 p = cl.particles + surfacelist[surfacelistindex];
2228 if (blendmode != particletype[p->typeindex].blendmode)
2230 blendmode = particletype[p->typeindex].blendmode;
2234 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2237 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2240 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2244 if (texture != particletexture[p->texnum].texture)
2246 texture = particletexture[p->texnum].texture;
2247 R_Mesh_TexBind(0, R_GetTexture(texture));
2250 // iterate until we find a change in settings
2251 batchstart = surfacelistindex++;
2252 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2254 p = cl.particles + surfacelist[surfacelistindex];
2255 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2259 batchcount = surfacelistindex - batchstart;
2260 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2262 GL_LockArrays(0, 0);
2265 void R_DrawParticles (void)
2268 float minparticledist;
2270 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2276 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2277 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2279 // LordHavoc: early out conditions
2280 if ((!cl.num_particles) || (!r_drawparticles.integer))
2283 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2284 gravity = frametime * cl.movevars_gravity;
2285 dvel = 1+4*frametime;
2286 decalfade = frametime * 255 / cl_decals_fadetime.value;
2287 update = frametime > 0;
2288 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2289 drawdist2 = drawdist2*drawdist2;
2291 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2295 if (cl.free_particle > i)
2296 cl.free_particle = i;
2302 if (p->delayedspawn > cl.time)
2304 p->delayedspawn = 0;
2308 p->size += p->sizeincrease * frametime;
2309 p->alpha -= p->alphafade * frametime;
2311 if (p->alpha <= 0 || p->die <= cl.time)
2314 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2316 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2318 if (p->typeindex == pt_blood)
2319 p->size += frametime * 8;
2321 p->vel[2] -= p->gravity * gravity;
2322 f = 1.0f - min(p->liquidfriction * frametime, 1);
2323 VectorScale(p->vel, f, p->vel);
2327 p->vel[2] -= p->gravity * gravity;
2330 f = 1.0f - min(p->airfriction * frametime, 1);
2331 VectorScale(p->vel, f, p->vel);
2335 VectorCopy(p->org, oldorg);
2336 VectorMA(p->org, frametime, p->vel, p->org);
2337 if (p->bounce && cl.time >= p->delayedcollisions)
2339 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);
2340 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2341 // or if the trace hit something flagged as NOIMPACT
2342 // then remove the particle
2343 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2345 VectorCopy(trace.endpos, p->org);
2346 // react if the particle hit something
2347 if (trace.fraction < 1)
2349 VectorCopy(trace.endpos, p->org);
2350 if (p->typeindex == pt_blood)
2352 // blood - splash on solid
2353 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2355 if (cl_stainmaps.integer)
2356 R_Stain(p->org, 32, 32, 16, 16, (int)(p->alpha * p->size * (1.0f / 40.0f)), 192, 48, 48, (int)(p->alpha * p->size * (1.0f / 40.0f)));
2357 if (cl_decals.integer)
2359 // create a decal for the blood splat
2360 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);
2364 else if (p->bounce < 0)
2366 // bounce -1 means remove on impact
2371 // anything else - bounce off solid
2372 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2373 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2374 if (DotProduct(p->vel, p->vel) < 0.03)
2375 VectorClear(p->vel);
2381 if (p->typeindex != pt_static)
2383 switch (p->typeindex)
2385 case pt_entityparticle:
2386 // particle that removes itself after one rendered frame
2393 a = CL_PointSuperContents(p->org);
2394 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2398 a = CL_PointSuperContents(p->org);
2399 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2403 a = CL_PointSuperContents(p->org);
2404 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2408 if (cl.time > p->time2)
2411 p->time2 = cl.time + (rand() & 3) * 0.1;
2412 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2413 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2415 a = CL_PointSuperContents(p->org);
2416 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2424 else if (p->delayedspawn)
2427 // don't render particles too close to the view (they chew fillrate)
2428 // also don't render particles behind the view (useless)
2429 // further checks to cull to the frustum would be too slow here
2430 switch(p->typeindex)
2433 // beams have no culling
2434 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2437 // anything else just has to be in front of the viewer and visible at this distance
2438 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2439 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2446 if (cl.free_particle > i)
2447 cl.free_particle = i;
2450 // reduce cl.num_particles if possible
2451 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2454 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2456 particle_t *oldparticles = cl.particles;
2457 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2458 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2459 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2460 Mem_Free(oldparticles);