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 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
31 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
32 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
33 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
34 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
35 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
36 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
38 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
39 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
40 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
41 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
44 #define PARTICLEEFFECT_UNDERWATER 1
45 #define PARTICLEEFFECT_NOTUNDERWATER 2
47 typedef struct particleeffectinfo_s
49 int effectnameindex; // which effect this belongs to
50 // PARTICLEEFFECT_* bits
52 // blood effects may spawn very few particles, so proper fraction-overflow
53 // handling is very important, this variable keeps track of the fraction
54 double particleaccumulator;
55 // the math is: countabsolute + requestedcount * countmultiplier * quality
56 // absolute number of particles to spawn, often used for decals
57 // (unaffected by quality and requestedcount)
59 // multiplier for the number of particles CL_ParticleEffect was told to
60 // spawn, most effects do not really have a count and hence use 1, so
61 // this is often the actual count to spawn, not merely a multiplier
62 float countmultiplier;
63 // if > 0 this causes the particle to spawn in an evenly spaced line from
64 // originmins to originmaxs (causing them to describe a trail, not a box)
66 // type of particle to spawn (defines some aspects of behavior)
68 // range of colors to choose from in hex RRGGBB (like HTML color tags),
69 // randomly interpolated at spawn
70 unsigned int color[2];
71 // a random texture is chosen in this range (note the second value is one
72 // past the last choosable, so for example 8,16 chooses any from 8 up and
74 // if start and end of the range are the same, no randomization is done
76 // range of size values randomly chosen when spawning, plus size increase over time
78 // range of alpha values randomly chosen when spawning, plus alpha fade
80 // how long the particle should live (note it is also removed if alpha drops to 0)
82 // how much gravity affects this particle (negative makes it fly up!)
84 // how much bounce the particle has when it hits a surface
85 // if negative the particle is removed on impact
87 // if in air this friction is applied
88 // if negative the particle accelerates
90 // if in liquid (water/slime/lava) this friction is applied
91 // if negative the particle accelerates
93 // these offsets are added to the values given to particleeffect(), and
94 // then an ellipsoid-shaped jitter is added as defined by these
95 // (they are the 3 radii)
96 float originoffset[3];
97 float velocityoffset[3];
98 float originjitter[3];
99 float velocityjitter[3];
100 float velocitymultiplier;
101 // an effect can also spawn a dlight
102 float lightradiusstart;
103 float lightradiusfade;
106 qboolean lightshadow;
109 particleeffectinfo_t;
111 #define MAX_PARTICLEEFFECTNAME 256
112 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
114 #define MAX_PARTICLEEFFECTINFO 4096
116 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
118 static int particlepalette[256] =
120 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
121 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
122 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
123 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
124 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
125 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
126 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
127 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
128 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
129 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
130 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
131 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
132 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
133 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
134 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
135 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
136 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
137 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
138 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
139 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
140 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
141 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
142 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
143 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
144 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
145 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
146 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
147 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
148 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
149 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
150 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
151 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
154 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
155 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
156 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
158 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
160 // texture numbers in particle font
161 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
162 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
163 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
164 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
165 static const int tex_rainsplash = 32;
166 static const int tex_particle = 63;
167 static const int tex_bubble = 62;
168 static const int tex_raindrop = 61;
169 static const int tex_beam = 60;
171 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
172 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
173 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
174 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
175 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
176 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
177 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5", "opacity of blood"};
178 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
179 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
180 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
181 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
182 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
183 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
184 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
185 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
186 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
187 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
188 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
189 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
190 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
191 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
192 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
195 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
201 particleeffectinfo_t *info = NULL;
202 const char *text = textstart;
204 effectinfoindex = -1;
205 for (linenumber = 1;;linenumber++)
208 for (arrayindex = 0;arrayindex < 16;arrayindex++)
209 argv[arrayindex][0] = 0;
212 if (!COM_ParseToken_Simple(&text, true, false))
214 if (!strcmp(com_token, "\n"))
218 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
224 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
225 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
226 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
227 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
228 #define readfloat(var) checkparms(2);var = atof(argv[1])
229 if (!strcmp(argv[0], "effect"))
234 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
236 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
239 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
241 if (particleeffectname[effectnameindex][0])
243 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
248 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
252 // if we run out of names, abort
253 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
255 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
258 info = particleeffectinfo + effectinfoindex;
259 info->effectnameindex = effectnameindex;
260 info->particletype = pt_alphastatic;
261 info->tex[0] = tex_particle;
262 info->tex[1] = tex_particle;
263 info->color[0] = 0xFFFFFF;
264 info->color[1] = 0xFFFFFF;
268 info->alpha[1] = 256;
269 info->alpha[2] = 256;
270 info->time[0] = 9999;
271 info->time[1] = 9999;
272 VectorSet(info->lightcolor, 1, 1, 1);
273 info->lightshadow = true;
274 info->lighttime = 9999;
276 else if (info == NULL)
278 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
281 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
282 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
283 else if (!strcmp(argv[0], "type"))
286 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
287 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
288 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
289 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
290 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
291 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
292 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
293 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
294 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
295 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
296 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
297 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
298 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
300 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
301 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
302 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
303 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
304 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
305 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
306 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
307 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
308 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
309 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
310 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
311 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
312 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
313 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
314 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
315 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
316 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
317 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
318 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
319 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
320 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
321 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
322 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
323 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
325 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
334 int CL_ParticleEffectIndexForName(const char *name)
337 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
338 if (!strcmp(particleeffectname[i], name))
343 const char *CL_ParticleEffectNameForIndex(int i)
345 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
347 return particleeffectname[i];
350 // MUST match effectnameindex_t in client.h
351 static const char *standardeffectnames[EFFECT_TOTAL] =
375 "TE_TEI_BIGEXPLOSION",
391 void CL_Particles_LoadEffectInfo(void)
394 unsigned char *filedata;
395 fs_offset_t filesize;
396 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
397 memset(particleeffectname, 0, sizeof(particleeffectname));
398 for (i = 0;i < EFFECT_TOTAL;i++)
399 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
400 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
403 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
413 void CL_ReadPointFile_f (void);
414 void CL_Particles_Init (void)
416 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
417 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
419 Cvar_RegisterVariable (&cl_particles);
420 Cvar_RegisterVariable (&cl_particles_quality);
421 Cvar_RegisterVariable (&cl_particles_alpha);
422 Cvar_RegisterVariable (&cl_particles_size);
423 Cvar_RegisterVariable (&cl_particles_quake);
424 Cvar_RegisterVariable (&cl_particles_blood);
425 Cvar_RegisterVariable (&cl_particles_blood_alpha);
426 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
427 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
428 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
429 Cvar_RegisterVariable (&cl_particles_explosions_shell);
430 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
431 Cvar_RegisterVariable (&cl_particles_rain);
432 Cvar_RegisterVariable (&cl_particles_snow);
433 Cvar_RegisterVariable (&cl_particles_smoke);
434 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
435 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
436 Cvar_RegisterVariable (&cl_particles_sparks);
437 Cvar_RegisterVariable (&cl_particles_bubbles);
438 Cvar_RegisterVariable (&cl_decals);
439 Cvar_RegisterVariable (&cl_decals_time);
440 Cvar_RegisterVariable (&cl_decals_fadetime);
443 void CL_Particles_Shutdown (void)
447 // list of all 26 parameters:
448 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
449 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
450 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
451 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
452 // palpha - opacity of particle as 0-255 (can be more than 255)
453 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
454 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
455 // pgravity - how much effect gravity has on the particle (0-1)
456 // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions
457 // px,py,pz - starting origin of particle
458 // pvx,pvy,pvz - starting velocity of particle
459 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
460 static particle_t *particle(particletype_t *ptype, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter)
465 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
466 if (cl.free_particle >= cl.max_particles)
468 part = &cl.particles[cl.free_particle++];
469 if (cl.num_particles < cl.free_particle)
470 cl.num_particles = cl.free_particle;
471 memset(part, 0, sizeof(*part));
473 l2 = (int)lhrandom(0.5, 256.5);
475 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
476 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
477 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
478 part->color[3] = 0xFF;
481 part->sizeincrease = psizeincrease;
482 part->alpha = palpha;
483 part->alphafade = palphafade;
484 part->gravity = pgravity;
485 part->bounce = pbounce;
487 part->org[0] = px + originjitter * v[0];
488 part->org[1] = py + originjitter * v[1];
489 part->org[2] = pz + originjitter * v[2];
490 part->vel[0] = pvx + velocityjitter * v[0];
491 part->vel[1] = pvy + velocityjitter * v[1];
492 part->vel[2] = pvz + velocityjitter * v[2];
494 part->airfriction = pairfriction;
495 part->liquidfriction = pliquidfriction;
496 part->die = cl.time + part->alpha / (part->alphafade ? part->alphafade : 1);
497 part->delayedcollisions = 0;
498 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
499 if (part->type == particletype + pt_rain)
503 float lifetime = part->die - cl.time;
506 // turn raindrop into simple spark and create delayedspawn splash effect
507 part->type = particletype + pt_spark;
509 VectorMA(part->org, lifetime, part->vel, endvec);
510 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((part->type == particletype + pt_rain || part->type == particletype + pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, NULL, false);
511 part->die = cl.time + lifetime * trace.fraction;
512 part2 = particle(particletype + pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0);
515 part2->delayedspawn = part->die;
516 part2->die += part->die - cl.time;
517 for (i = rand() & 7;i < 10;i++)
519 part2 = particle(particletype + pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32);
522 part2->delayedspawn = part->die;
523 part2->die += part->die - cl.time;
528 else if (part->bounce != 0 && part->gravity == 0)
530 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
533 VectorMA(part->org, lifetime, part->vel, endvec);
534 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((part->type == particletype + pt_rain || part->type == particletype + pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, NULL, false);
535 part->delayedcollisions = cl.time + lifetime * trace.fraction;
540 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
543 if (!cl_decals.integer)
545 p = particle(particletype + pt_decal, color1, color2, texnum, size, 0, alpha, 0, 0, 0, org[0] + normal[0], org[1] + normal[1], org[2] + normal[2], normal[0], normal[1], normal[2], 0, 0, 0, 0);
550 p->ownermodel = cl.entities[p->owner].render.model;
551 VectorAdd(org, normal, p->org);
552 VectorCopy(normal, p->vel);
553 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
554 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
555 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
559 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
562 float bestfrac, bestorg[3], bestnormal[3];
564 int besthitent = 0, hitent;
567 for (i = 0;i < 32;i++)
570 VectorMA(org, maxdist, org2, org2);
571 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
572 // take the closest trace result that doesn't end up hitting a NOMARKS
573 // surface (sky for example)
574 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
576 bestfrac = trace.fraction;
578 VectorCopy(trace.endpos, bestorg);
579 VectorCopy(trace.plane.normal, bestnormal);
583 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
586 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
587 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
588 void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
591 matrix4x4_t tempmatrix;
592 VectorLerp(originmins, 0.5, originmaxs, center);
593 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
594 if (effectnameindex == EFFECT_SVC_PARTICLE)
596 if (cl_particles.integer)
598 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
600 CL_ParticleExplosion(center);
601 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
602 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
605 count *= cl_particles_quality.value;
606 for (;count > 0;count--)
608 int k = particlepalette[palettecolor + (rand()&7)];
609 if (cl_particles_quake.integer)
610 particle(particletype + pt_alphastatic, k, k, tex_particle, 1.5, 0, lhrandom(51, 255), 512, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0);
611 else if (gamemode == GAME_GOODVSBAD2)
612 particle(particletype + pt_alphastatic, k, k, tex_particle, 5, 0, 255, 300, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 10);
614 particle(particletype + pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 15);
619 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
620 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
621 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
622 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
623 else if (effectnameindex == EFFECT_TE_SPIKE)
625 if (cl_particles_bulletimpacts.integer)
627 if (cl_particles_quake.integer)
629 if (cl_particles_smoke.integer)
630 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
634 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
635 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
639 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
640 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
642 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
644 if (cl_particles_bulletimpacts.integer)
646 if (cl_particles_quake.integer)
648 if (cl_particles_smoke.integer)
649 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
653 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
654 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
658 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
659 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
660 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);
662 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
664 if (cl_particles_bulletimpacts.integer)
666 if (cl_particles_quake.integer)
668 if (cl_particles_smoke.integer)
669 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
673 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
674 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
678 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
679 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
681 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
683 if (cl_particles_bulletimpacts.integer)
685 if (cl_particles_quake.integer)
687 if (cl_particles_smoke.integer)
688 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
692 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
693 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
697 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
698 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
699 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);
701 else if (effectnameindex == EFFECT_TE_BLOOD)
703 if (!cl_particles_blood.integer)
705 if (cl_particles_quake.integer)
706 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
709 static double bloodaccumulator = 0;
710 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
711 for (;bloodaccumulator > 0;bloodaccumulator--)
712 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 0, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
715 else if (effectnameindex == EFFECT_TE_SPARK)
716 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
717 else if (effectnameindex == EFFECT_TE_PLASMABURN)
719 // plasma scorch mark
720 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
721 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
722 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
724 else if (effectnameindex == EFFECT_TE_GUNSHOT)
726 if (cl_particles_bulletimpacts.integer)
728 if (cl_particles_quake.integer)
729 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
732 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
733 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
737 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
738 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
740 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
742 if (cl_particles_bulletimpacts.integer)
744 if (cl_particles_quake.integer)
745 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
748 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
749 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
753 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
754 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
755 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);
757 else if (effectnameindex == EFFECT_TE_EXPLOSION)
759 CL_ParticleExplosion(center);
760 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);
762 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
764 CL_ParticleExplosion(center);
765 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);
767 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
769 if (cl_particles_quake.integer)
772 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
775 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
777 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
781 CL_ParticleExplosion(center);
782 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);
784 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
785 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);
786 else if (effectnameindex == EFFECT_TE_FLAMEJET)
788 count *= cl_particles_quality.value;
790 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128);
792 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
794 float i, j, inc, vel;
797 inc = 8 / cl_particles_quality.value;
798 for (i = -128;i < 128;i += inc)
800 for (j = -128;j < 128;j += inc)
802 dir[0] = j + lhrandom(0, inc);
803 dir[1] = i + lhrandom(0, inc);
805 org[0] = center[0] + dir[0];
806 org[1] = center[1] + dir[1];
807 org[2] = center[2] + lhrandom(0, 64);
808 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
809 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, inc * lhrandom(24, 32), inc * 12, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
813 else if (effectnameindex == EFFECT_TE_TELEPORT)
815 float i, j, k, inc, vel;
818 inc = 8 / cl_particles_quality.value;
819 for (i = -16;i < 16;i += inc)
821 for (j = -16;j < 16;j += inc)
823 for (k = -24;k < 32;k += inc)
825 VectorSet(dir, i*8, j*8, k*8);
826 VectorNormalize(dir);
827 vel = lhrandom(50, 113);
828 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0);
832 particle(particletype + pt_static, particlepalette[14], particlepalette[14], tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0);
833 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);
835 else if (effectnameindex == EFFECT_TE_TEI_G3)
836 particle(particletype + pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
837 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
839 if (cl_particles_smoke.integer)
841 count *= 0.25f * cl_particles_quality.value;
843 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f);
846 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
848 CL_ParticleExplosion(center);
849 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);
851 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
854 if (cl_stainmaps.integer)
855 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
856 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
857 if (cl_particles_smoke.integer)
858 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
859 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155);
860 if (cl_particles_sparks.integer)
861 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
862 particle(particletype + pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465);
863 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);
865 else if (effectnameindex == EFFECT_EF_FLAME)
867 count *= 300 * cl_particles_quality.value;
869 particle(particletype + pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128);
870 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);
872 else if (effectnameindex == EFFECT_EF_STARDUST)
874 count *= 200 * cl_particles_quality.value;
876 particle(particletype + pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128);
877 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);
879 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
883 int smoke, blood, bubbles, r, color;
885 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
888 Vector4Set(light, 0, 0, 0, 0);
890 if (effectnameindex == EFFECT_TR_ROCKET)
891 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
892 else if (effectnameindex == EFFECT_TR_VORESPIKE)
894 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
895 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
897 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
899 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
900 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
904 matrix4x4_t tempmatrix;
905 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
906 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
913 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
916 VectorSubtract(originmaxs, originmins, dir);
917 len = VectorNormalizeLength(dir);
920 dec = -ent->persistent.trail_time;
921 ent->persistent.trail_time += len;
922 if (ent->persistent.trail_time < 0.01f)
925 // if we skip out, leave it reset
926 ent->persistent.trail_time = 0.0f;
931 // advance into this frame to reach the first puff location
932 VectorMA(originmins, dec, dir, pos);
935 smoke = cl_particles.integer && cl_particles_smoke.integer;
936 blood = cl_particles.integer && cl_particles_blood.integer;
937 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
938 qd = 1.0f / cl_particles_quality.value;
945 if (effectnameindex == EFFECT_TR_BLOOD)
947 if (cl_particles_quake.integer)
949 color = particlepalette[67 + (rand()&3)];
950 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
955 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
958 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
960 if (cl_particles_quake.integer)
963 color = particlepalette[67 + (rand()&3)];
964 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
969 particle(particletype + pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 0, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64);
975 if (effectnameindex == EFFECT_TR_ROCKET)
977 if (cl_particles_quake.integer)
980 color = particlepalette[ramp3[r]];
981 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
985 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
986 particle(particletype + pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20);
989 else if (effectnameindex == EFFECT_TR_GRENADE)
991 if (cl_particles_quake.integer)
994 color = particlepalette[ramp3[r]];
995 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
999 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1002 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1004 if (cl_particles_quake.integer)
1007 color = particlepalette[52 + (rand()&7)];
1008 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1009 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1011 else if (gamemode == GAME_GOODVSBAD2)
1014 particle(particletype + pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1018 color = particlepalette[20 + (rand()&7)];
1019 particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1022 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1024 if (cl_particles_quake.integer)
1027 color = particlepalette[230 + (rand()&7)];
1028 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
1029 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
1033 color = particlepalette[226 + (rand()&7)];
1034 particle(particletype + pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1037 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1039 if (cl_particles_quake.integer)
1041 color = particlepalette[152 + (rand()&3)];
1042 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
1044 else if (gamemode == GAME_GOODVSBAD2)
1047 particle(particletype + pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1049 else if (gamemode == GAME_PRYDON)
1052 particle(particletype + pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1055 particle(particletype + pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1057 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1060 particle(particletype + pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4);
1062 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1065 particle(particletype + pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16);
1067 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1068 particle(particletype + pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
1072 if (effectnameindex == EFFECT_TR_ROCKET)
1073 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1074 else if (effectnameindex == EFFECT_TR_GRENADE)
1075 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(64, 255), 256, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16);
1077 // advance to next time and position
1080 VectorMA (pos, dec, dir, pos);
1083 ent->persistent.trail_time = len;
1085 else if (developer.integer >= 1)
1086 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1089 // this is also called on point effects with spawndlight = true and
1090 // spawnparticles = true
1091 // it is called CL_ParticleTrail because most code does not want to supply
1092 // these parameters, only trail handling does
1093 void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles)
1096 qboolean found = false;
1097 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1098 return; // invalid effect index
1099 if (!particleeffectname[effectnameindex][0])
1100 return; // no such effect
1101 VectorLerp(originmins, 0.5, originmaxs, center);
1102 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1104 int effectinfoindex;
1107 particleeffectinfo_t *info;
1109 vec3_t centervelocity;
1115 qboolean underwater;
1116 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1117 VectorLerp(originmins, 0.5, originmaxs, center);
1118 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1119 supercontents = CL_PointSuperContents(center);
1120 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1121 VectorSubtract(originmaxs, originmins, traildir);
1122 traillen = VectorLength(traildir);
1123 VectorNormalize(traildir);
1124 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1126 if (info->effectnameindex == effectnameindex)
1129 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1131 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1134 // spawn a dlight if requested
1135 if (info->lightradiusstart > 0 && spawndlight)
1137 matrix4x4_t tempmatrix;
1138 if (info->trailspacing > 0)
1139 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1141 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1142 if (info->lighttime > 0 && info->lightradiusfade > 0)
1144 // light flash (explosion, etc)
1145 // called when effect starts
1146 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);
1151 // called by CL_LinkNetworkEntity
1152 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1153 R_RTLight_Update(&r_refdef.lights[r_refdef.numlights++], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1157 if (!spawnparticles)
1162 if (info->tex[1] > info->tex[0])
1164 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1165 tex = min(tex, info->tex[1] - 1);
1167 if (info->particletype == pt_decal)
1168 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]);
1169 else if (info->particletype == pt_beam)
1170 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0);
1173 if (!cl_particles.integer)
1175 switch (info->particletype)
1177 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1178 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1179 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1180 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1181 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1182 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1185 VectorCopy(originmins, trailpos);
1186 if (info->trailspacing > 0)
1188 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1189 trailstep = info->trailspacing / cl_particles_quality.value;
1193 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1196 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1197 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1199 if (info->tex[1] > info->tex[0])
1201 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1202 tex = min(tex, info->tex[1] - 1);
1206 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1207 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1208 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1211 particle(particletype + info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0);
1213 VectorMA(trailpos, trailstep, traildir, trailpos);
1220 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1223 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)
1225 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1233 void CL_EntityParticles (const entity_t *ent)
1236 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1237 static vec3_t avelocities[NUMVERTEXNORMALS];
1238 if (!cl_particles.integer) return;
1240 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1242 if (!avelocities[0][0])
1243 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1244 avelocities[0][i] = lhrandom(0, 2.55);
1246 for (i = 0;i < NUMVERTEXNORMALS;i++)
1248 yaw = cl.time * avelocities[i][0];
1249 pitch = cl.time * avelocities[i][1];
1250 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1251 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1252 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1253 particle(particletype + pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0);
1258 void CL_ReadPointFile_f (void)
1260 vec3_t org, leakorg;
1262 char *pointfile = NULL, *pointfilepos, *t, tchar;
1263 char name[MAX_OSPATH];
1268 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1269 strlcat (name, ".pts", sizeof (name));
1270 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1273 Con_Printf("Could not open %s\n", name);
1277 Con_Printf("Reading %s...\n", name);
1278 VectorClear(leakorg);
1281 pointfilepos = pointfile;
1282 while (*pointfilepos)
1284 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1289 while (*t && *t != '\n' && *t != '\r')
1293 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1299 VectorCopy(org, leakorg);
1302 if (cl.num_particles < cl.max_particles - 3)
1305 particle(particletype + pt_static, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0);
1308 Mem_Free(pointfile);
1309 VectorCopy(leakorg, org);
1310 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1312 particle(particletype + pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0);
1313 particle(particletype + pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0);
1314 particle(particletype + pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0);
1319 CL_ParseParticleEffect
1321 Parse an effect out of the server message
1324 void CL_ParseParticleEffect (void)
1327 int i, count, msgcount, color;
1329 MSG_ReadVector(org, cls.protocol);
1330 for (i=0 ; i<3 ; i++)
1331 dir[i] = MSG_ReadChar ();
1332 msgcount = MSG_ReadByte ();
1333 color = MSG_ReadByte ();
1335 if (msgcount == 255)
1340 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1345 CL_ParticleExplosion
1349 void CL_ParticleExplosion (const vec3_t org)
1355 if (cl_stainmaps.integer)
1356 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1357 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1359 if (cl_particles_quake.integer)
1361 for (i = 0;i < 1024;i++)
1367 color = particlepalette[ramp1[r]];
1368 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1372 color = particlepalette[ramp2[r]];
1373 particle(particletype + pt_alphastatic, color, color, tex_particle, 1.5f, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1379 i = CL_PointSuperContents(org);
1380 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1382 if (cl_particles.integer && cl_particles_bubbles.integer)
1383 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1384 particle(particletype + pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96);
1388 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1390 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1392 for (i = 0;i < 32;i++)
1396 for (k = 0;k < 16;k++)
1398 v[0] = org[0] + lhrandom(-48, 48);
1399 v[1] = org[1] + lhrandom(-48, 48);
1400 v[2] = org[2] + lhrandom(-48, 48);
1401 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1402 if (trace.fraction >= 0.1)
1405 VectorSubtract(trace.endpos, org, v2);
1406 VectorScale(v2, 2.0f, v2);
1407 particle(particletype + pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 12, 0, 32, 64, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1411 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1413 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1417 for (k = 0;k < 16;k++)
1420 VectorMA(org, 128, v2, v);
1421 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1422 if (trace.fraction >= 0.1)
1425 VectorSubtract(trace.endpos, org, v2);
1426 VectorScale(v2, 2.0f, v2);
1427 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0);
1433 if (cl_particles_explosions_shell.integer)
1434 R_NewExplosion(org);
1439 CL_ParticleExplosion2
1443 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1446 if (!cl_particles.integer) return;
1448 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1450 k = particlepalette[colorStart + (i % colorLength)];
1451 if (cl_particles_quake.integer)
1452 particle(particletype + pt_static, k, k, tex_particle, 1, 0, 255, 850, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 8, 256);
1454 particle(particletype + pt_static, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192);
1458 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1460 if (cl_particles_sparks.integer)
1462 sparkcount *= cl_particles_quality.value;
1463 while(sparkcount-- > 0)
1464 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64);
1468 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1470 if (cl_particles_smoke.integer)
1472 smokecount *= cl_particles_quality.value;
1473 while(smokecount-- > 0)
1474 particle(particletype + pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0);
1478 void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel)
1481 if (!cl_particles.integer) return;
1483 count = (int)(count * cl_particles_quality.value);
1486 k = particlepalette[colorbase + (rand()&3)];
1487 particle(particletype + pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel);
1491 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1494 float z, minz, maxz;
1496 if (!cl_particles.integer) return;
1497 if (dir[2] < 0) // falling
1502 minz = z - fabs(dir[2]) * 0.1;
1503 maxz = z + fabs(dir[2]) * 0.1;
1504 minz = bound(mins[2], minz, maxs[2]);
1505 maxz = bound(mins[2], maxz, maxs[2]);
1507 count = (int)(count * cl_particles_quality.value);
1512 if (!cl_particles_rain.integer) break;
1513 count *= 4; // ick, this should be in the mod or maps?
1517 k = particlepalette[colorbase + (rand()&3)];
1518 if (gamemode == GAME_GOODVSBAD2)
1519 particle(particletype + pt_rain, k, k, tex_particle, 20, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1521 particle(particletype + pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(8, 16), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1525 if (!cl_particles_snow.integer) break;
1528 k = particlepalette[colorbase + (rand()&3)];
1529 if (gamemode == GAME_GOODVSBAD2)
1530 p = particle(particletype + pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1532 p = particle(particletype + pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0);
1534 VectorCopy(p->vel, p->relativedirection);
1538 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1547 void CL_MoveParticles (void)
1550 int i, maxparticle, j, a, content;
1551 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1552 particletype_t *decaltype, *bloodtype;
1556 // LordHavoc: early out condition
1557 if (!cl.num_particles)
1559 cl.free_particle = 0;
1563 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1564 gravity = frametime * cl.movevars_gravity;
1565 dvel = 1+4*frametime;
1566 decalfade = frametime * 255 / cl_decals_fadetime.value;
1567 decaltype = particletype + pt_decal;
1568 bloodtype = particletype + pt_blood;
1572 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1576 if (cl.free_particle > i)
1577 cl.free_particle = i;
1582 // heavily optimized decal case
1583 if (p->type == decaltype)
1585 // FIXME: this has fairly wacky handling of alpha
1586 if (cl.time > p->time2 + cl_decals_time.value)
1588 p->alpha -= decalfade;
1592 if (cl.free_particle > i)
1593 cl.free_particle = i;
1599 if (cl.entities[p->owner].render.model == p->ownermodel)
1601 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1602 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1607 if (cl.free_particle > i)
1608 cl.free_particle = i;
1614 if (p->delayedspawn)
1616 if (p->delayedspawn > cl.time)
1618 p->delayedspawn = 0;
1623 p->size += p->sizeincrease * frametime;
1624 p->alpha -= p->alphafade * frametime;
1626 if (p->alpha <= 0 || p->die <= cl.time)
1629 if (cl.free_particle > i)
1630 cl.free_particle = i;
1634 if (p->type->orientation != PARTICLE_BEAM)
1636 VectorCopy(p->org, oldorg);
1637 VectorMA(p->org, frametime, p->vel, p->org);
1638 VectorCopy(p->org, org);
1639 if (p->bounce && cl.time >= p->delayedcollisions)
1641 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | (p->type == particletype + pt_rain ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
1642 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1643 // or if the trace hit something flagged as NOIMPACT
1644 // then remove the particle
1645 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1650 // react if the particle hit something
1651 if (trace.fraction < 1)
1653 VectorCopy(trace.endpos, p->org);
1654 if (p->type == particletype + pt_rain)
1656 // raindrop - splash on solid/water/slime/lava
1658 // convert from a raindrop particle to a rainsplash decal
1659 VectorCopy(trace.plane.normal, p->vel);
1660 VectorAdd(p->org, p->vel, p->org);
1661 p->type = particletype + pt_raindecal;
1662 p->texnum = tex_rainsplash;
1664 p->alphafade = p->alpha / 0.4;
1667 p->liquidfriction = 0;
1670 p->sizeincrease = p->size * 20;
1671 count = (int)lhrandom(1, 10);
1673 particle(particletype + pt_spark, 0x000000, 0x707070, tex_particle, 0.25f, 0, lhrandom(64, 255), 512, 1, 0, p->org[0], p->org[1], p->org[2], p->vel[0]*16, p->vel[1]*16, cl.movevars_gravity * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
1675 else if (p->type == bloodtype)
1677 // blood - splash on solid
1678 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1683 if (cl_stainmaps.integer)
1684 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)));
1685 if (!cl_decals.integer)
1690 // convert from a blood particle to a blood decal
1691 VectorCopy(trace.plane.normal, p->vel);
1692 VectorAdd(p->org, p->vel, p->org);
1694 p->type = particletype + pt_decal;
1695 p->texnum = tex_blooddecal[rand()&7];
1697 p->ownermodel = cl.entities[hitent].render.model;
1698 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1699 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1700 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1705 p->liquidfriction = 0;
1709 else if (p->bounce < 0)
1711 // bounce -1 means remove on impact
1717 // anything else - bounce off solid
1718 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1719 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1720 if (DotProduct(p->vel, p->vel) < 0.03)
1721 VectorClear(p->vel);
1725 p->vel[2] -= p->gravity * gravity;
1727 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1729 f = 1.0f - min(p->liquidfriction * frametime, 1);
1730 VectorScale(p->vel, f, p->vel);
1732 else if (p->airfriction)
1734 f = 1.0f - min(p->airfriction * frametime, 1);
1735 VectorScale(p->vel, f, p->vel);
1739 if (p->type != particletype + pt_static)
1741 switch (p->type - particletype)
1743 case pt_entityparticle:
1744 // particle that removes itself after one rendered frame
1751 a = CL_PointSuperContents(p->org);
1752 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1754 p->size += frametime * 8;
1755 //p->alpha -= bloodwaterfade;
1758 p->vel[2] -= gravity;
1759 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1763 a = CL_PointSuperContents(p->org);
1764 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1771 a = CL_PointSuperContents(p->org);
1772 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1776 if (cl.time > p->time2)
1779 p->time2 = cl.time + (rand() & 3) * 0.1;
1780 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1781 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1782 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1784 a = CL_PointSuperContents(p->org);
1785 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1793 cl.num_particles = maxparticle + 1;
1796 #define MAX_PARTICLETEXTURES 64
1797 // particletexture_t is a rectangle in the particlefonttexture
1798 typedef struct particletexture_s
1800 rtexture_t *texture;
1801 float s1, t1, s2, t2;
1805 static rtexturepool_t *particletexturepool;
1806 static rtexture_t *particlefonttexture;
1807 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1809 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1811 #define PARTICLETEXTURESIZE 64
1812 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1814 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1818 dz = 1 - (dx*dx+dy*dy);
1819 if (dz > 0) // it does hit the sphere
1823 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1824 VectorNormalize(normal);
1825 dot = DotProduct(normal, light);
1826 if (dot > 0.5) // interior reflection
1827 f += ((dot * 2) - 1);
1828 else if (dot < -0.5) // exterior reflection
1829 f += ((dot * -2) - 1);
1831 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1832 VectorNormalize(normal);
1833 dot = DotProduct(normal, light);
1834 if (dot > 0.5) // interior reflection
1835 f += ((dot * 2) - 1);
1836 else if (dot < -0.5) // exterior reflection
1837 f += ((dot * -2) - 1);
1839 f += 16; // just to give it a haze so you can see the outline
1840 f = bound(0, f, 255);
1841 return (unsigned char) f;
1847 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1849 int basex, basey, y;
1850 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1851 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1852 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1853 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1856 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1859 float cx, cy, dx, dy, f, iradius;
1861 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1862 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1863 iradius = 1.0f / radius;
1864 alpha *= (1.0f / 255.0f);
1865 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1867 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1871 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1874 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1875 d[0] += (int)(f * (red - d[0]));
1876 d[1] += (int)(f * (green - d[1]));
1877 d[2] += (int)(f * (blue - d[2]));
1883 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1886 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1888 data[0] = bound(minr, data[0], maxr);
1889 data[1] = bound(ming, data[1], maxg);
1890 data[2] = bound(minb, data[2], maxb);
1894 void particletextureinvert(unsigned char *data)
1897 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1899 data[0] = 255 - data[0];
1900 data[1] = 255 - data[1];
1901 data[2] = 255 - data[2];
1905 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1906 static void R_InitBloodTextures (unsigned char *particletexturedata)
1909 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1912 for (i = 0;i < 8;i++)
1914 memset(&data[0][0][0], 255, sizeof(data));
1915 for (k = 0;k < 24;k++)
1916 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1917 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1918 particletextureinvert(&data[0][0][0]);
1919 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1923 for (i = 0;i < 8;i++)
1925 memset(&data[0][0][0], 255, sizeof(data));
1927 for (j = 1;j < 10;j++)
1928 for (k = min(j, m - 1);k < m;k++)
1929 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1930 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1931 particletextureinvert(&data[0][0][0]);
1932 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1937 //uncomment this to make engine save out particle font to a tga file when run
1938 //#define DUMPPARTICLEFONT
1940 static void R_InitParticleTexture (void)
1942 int x, y, d, i, k, m;
1946 // a note: decals need to modulate (multiply) the background color to
1947 // properly darken it (stain), and they need to be able to alpha fade,
1948 // this is a very difficult challenge because it means fading to white
1949 // (no change to background) rather than black (darkening everything
1950 // behind the whole decal polygon), and to accomplish this the texture is
1951 // inverted (dark red blood on white background becomes brilliant cyan
1952 // and white on black background) so we can alpha fade it to black, then
1953 // we invert it again during the blendfunc to make it work...
1955 #ifndef DUMPPARTICLEFONT
1956 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1957 if (!particlefonttexture)
1960 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1961 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1962 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1965 for (i = 0;i < 8;i++)
1967 memset(&data[0][0][0], 255, sizeof(data));
1970 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1972 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1973 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1975 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1977 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1978 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1980 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1981 d = (noise2[y][x] - 128) * 3 + 192;
1983 d = (int)(d * (1-(dx*dx+dy*dy)));
1984 d = (d * noise1[y][x]) >> 7;
1985 d = bound(0, d, 255);
1986 data[y][x][3] = (unsigned char) d;
1993 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1997 memset(&data[0][0][0], 255, sizeof(data));
1998 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2000 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2001 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2003 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2004 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2005 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
2008 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
2011 memset(&data[0][0][0], 255, sizeof(data));
2012 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2014 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2015 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2017 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2018 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2019 d = bound(0, d, 255);
2020 data[y][x][3] = (unsigned char) d;
2023 setuptex(tex_particle, &data[0][0][0], particletexturedata);
2026 memset(&data[0][0][0], 255, sizeof(data));
2027 light[0] = 1;light[1] = 1;light[2] = 1;
2028 VectorNormalize(light);
2029 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2031 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2032 // stretch upper half of bubble by +50% and shrink lower half by -50%
2033 // (this gives an elongated teardrop shape)
2035 dy = (dy - 0.5f) * 2.0f;
2037 dy = (dy - 0.5f) / 1.5f;
2038 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2040 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2041 // shrink bubble width to half
2043 data[y][x][3] = shadebubble(dx, dy, light);
2046 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2049 memset(&data[0][0][0], 255, sizeof(data));
2050 light[0] = 1;light[1] = 1;light[2] = 1;
2051 VectorNormalize(light);
2052 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2054 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2055 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2057 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2058 data[y][x][3] = shadebubble(dx, dy, light);
2061 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2063 // Blood particles and blood decals
2064 R_InitBloodTextures (particletexturedata);
2067 for (i = 0;i < 8;i++)
2069 memset(&data[0][0][0], 255, sizeof(data));
2070 for (k = 0;k < 12;k++)
2071 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2072 for (k = 0;k < 3;k++)
2073 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2074 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2075 particletextureinvert(&data[0][0][0]);
2076 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2079 #ifdef DUMPPARTICLEFONT
2080 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2083 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2085 Mem_Free(particletexturedata);
2087 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2089 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2090 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2091 particletexture[i].texture = particlefonttexture;
2092 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2093 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2094 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2095 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2098 #ifndef DUMPPARTICLEFONT
2099 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2100 if (!particletexture[tex_beam].texture)
2103 unsigned char noise3[64][64], data2[64][16][4];
2105 fractalnoise(&noise3[0][0], 64, 4);
2107 for (y = 0;y < 64;y++)
2109 dy = (y - 0.5f*64) / (64*0.5f-1);
2110 for (x = 0;x < 16;x++)
2112 dx = (x - 0.5f*16) / (16*0.5f-2);
2113 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2114 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2115 data2[y][x][3] = 255;
2119 #ifdef DUMPPARTICLEFONT
2120 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2122 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2124 particletexture[tex_beam].s1 = 0;
2125 particletexture[tex_beam].t1 = 0;
2126 particletexture[tex_beam].s2 = 1;
2127 particletexture[tex_beam].t2 = 1;
2130 static void r_part_start(void)
2132 particletexturepool = R_AllocTexturePool();
2133 R_InitParticleTexture ();
2134 CL_Particles_LoadEffectInfo();
2137 static void r_part_shutdown(void)
2139 R_FreeTexturePool(&particletexturepool);
2142 static void r_part_newmap(void)
2146 #define BATCHSIZE 256
2147 int particle_element3i[BATCHSIZE*6];
2148 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2150 void R_Particles_Init (void)
2153 for (i = 0;i < BATCHSIZE;i++)
2155 particle_element3i[i*6+0] = i*4+0;
2156 particle_element3i[i*6+1] = i*4+1;
2157 particle_element3i[i*6+2] = i*4+2;
2158 particle_element3i[i*6+3] = i*4+0;
2159 particle_element3i[i*6+4] = i*4+2;
2160 particle_element3i[i*6+5] = i*4+3;
2163 Cvar_RegisterVariable(&r_drawparticles);
2164 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2167 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2169 int surfacelistindex;
2170 int batchstart, batchcount;
2171 const particle_t *p;
2173 rtexture_t *texture;
2174 float *v3f, *t2f, *c4f;
2176 R_Mesh_Matrix(&identitymatrix);
2177 R_Mesh_ResetTextureState();
2178 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2179 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2180 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2181 GL_DepthMask(false);
2182 GL_DepthRange(0, 1);
2183 GL_PolygonOffset(0, 0);
2185 GL_CullFace(GL_NONE);
2187 // first generate all the vertices at once
2188 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2190 particletexture_t *tex;
2192 float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
2194 p = cl.particles + surfacelist[surfacelistindex];
2196 blendmode = p->type->blendmode;
2198 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2199 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2200 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2201 ca = p->alpha * (1.0f / 255.0f);
2202 if (blendmode == PBLEND_MOD)
2212 ca *= cl_particles_alpha.value;
2213 if (p->type->lighting)
2215 float ambient[3], diffuse[3], diffusenormal[3];
2216 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2217 cr *= (ambient[0] + 0.5 * diffuse[0]);
2218 cg *= (ambient[1] + 0.5 * diffuse[1]);
2219 cb *= (ambient[2] + 0.5 * diffuse[2]);
2221 if (r_refdef.fogenabled)
2223 fog = FogPoint_World(p->org);
2227 if (blendmode == PBLEND_ALPHA)
2230 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2231 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2232 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2235 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2236 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2237 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2238 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2240 size = p->size * cl_particles_size.value;
2242 tex = &particletexture[p->texnum];
2243 if (p->type->orientation == PARTICLE_BILLBOARD)
2245 VectorScale(r_view.left, -size, right);
2246 VectorScale(r_view.up, size, up);
2247 v3f[ 0] = org[0] - right[0] - up[0];
2248 v3f[ 1] = org[1] - right[1] - up[1];
2249 v3f[ 2] = org[2] - right[2] - up[2];
2250 v3f[ 3] = org[0] - right[0] + up[0];
2251 v3f[ 4] = org[1] - right[1] + up[1];
2252 v3f[ 5] = org[2] - right[2] + up[2];
2253 v3f[ 6] = org[0] + right[0] + up[0];
2254 v3f[ 7] = org[1] + right[1] + up[1];
2255 v3f[ 8] = org[2] + right[2] + up[2];
2256 v3f[ 9] = org[0] + right[0] - up[0];
2257 v3f[10] = org[1] + right[1] - up[1];
2258 v3f[11] = org[2] + right[2] - up[2];
2259 t2f[0] = tex->s1;t2f[1] = tex->t2;
2260 t2f[2] = tex->s1;t2f[3] = tex->t1;
2261 t2f[4] = tex->s2;t2f[5] = tex->t1;
2262 t2f[6] = tex->s2;t2f[7] = tex->t2;
2264 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2266 VectorVectors(p->vel, right, up);
2267 VectorScale(right, size, right);
2268 VectorScale(up, size, up);
2269 v3f[ 0] = org[0] - right[0] - up[0];
2270 v3f[ 1] = org[1] - right[1] - up[1];
2271 v3f[ 2] = org[2] - right[2] - up[2];
2272 v3f[ 3] = org[0] - right[0] + up[0];
2273 v3f[ 4] = org[1] - right[1] + up[1];
2274 v3f[ 5] = org[2] - right[2] + up[2];
2275 v3f[ 6] = org[0] + right[0] + up[0];
2276 v3f[ 7] = org[1] + right[1] + up[1];
2277 v3f[ 8] = org[2] + right[2] + up[2];
2278 v3f[ 9] = org[0] + right[0] - up[0];
2279 v3f[10] = org[1] + right[1] - up[1];
2280 v3f[11] = org[2] + right[2] - up[2];
2281 t2f[0] = tex->s1;t2f[1] = tex->t2;
2282 t2f[2] = tex->s1;t2f[3] = tex->t1;
2283 t2f[4] = tex->s2;t2f[5] = tex->t1;
2284 t2f[6] = tex->s2;t2f[7] = tex->t2;
2286 else if (p->type->orientation == PARTICLE_SPARK)
2288 VectorMA(org, -0.02, p->vel, v);
2289 VectorMA(org, 0.02, p->vel, up2);
2290 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2291 t2f[0] = tex->s1;t2f[1] = tex->t2;
2292 t2f[2] = tex->s1;t2f[3] = tex->t1;
2293 t2f[4] = tex->s2;t2f[5] = tex->t1;
2294 t2f[6] = tex->s2;t2f[7] = tex->t2;
2296 else if (p->type->orientation == PARTICLE_BEAM)
2298 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2299 VectorSubtract(p->vel, org, up);
2300 VectorNormalize(up);
2301 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2302 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2303 t2f[0] = 1;t2f[1] = v[0];
2304 t2f[2] = 0;t2f[3] = v[0];
2305 t2f[4] = 0;t2f[5] = v[1];
2306 t2f[6] = 1;t2f[7] = v[1];
2310 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2315 // now render batches of particles based on blendmode and texture
2316 blendmode = PBLEND_ADD;
2317 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2318 texture = particletexture[63].texture;
2319 R_Mesh_TexBind(0, R_GetTexture(texture));
2320 GL_LockArrays(0, numsurfaces*4);
2323 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2325 p = cl.particles + surfacelist[surfacelistindex];
2327 if (blendmode != p->type->blendmode)
2330 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2332 batchstart = surfacelistindex;
2333 blendmode = p->type->blendmode;
2334 if (blendmode == PBLEND_ALPHA)
2335 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2336 else if (blendmode == PBLEND_ADD)
2337 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2338 else //if (blendmode == PBLEND_MOD)
2339 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2341 if (texture != particletexture[p->texnum].texture)
2344 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2346 batchstart = surfacelistindex;
2347 texture = particletexture[p->texnum].texture;
2348 R_Mesh_TexBind(0, R_GetTexture(texture));
2354 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2355 GL_LockArrays(0, 0);
2358 void R_DrawParticles (void)
2361 float minparticledist;
2364 // LordHavoc: early out conditions
2365 if ((!cl.num_particles) || (!r_drawparticles.integer))
2368 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2370 // LordHavoc: only render if not too close
2371 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2373 if (p->type && !p->delayedspawn)
2375 r_refdef.stats.particles++;
2376 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2377 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);