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 {0, 0, false}, // pt_dead
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_MOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_MOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // range of colors to choose from in hex RRGGBB (like HTML color tags),
70 // randomly interpolated at spawn
71 unsigned int color[2];
72 // a random texture is chosen in this range (note the second value is one
73 // past the last choosable, so for example 8,16 chooses any from 8 up and
75 // if start and end of the range are the same, no randomization is done
77 // range of size values randomly chosen when spawning, plus size increase over time
79 // range of alpha values randomly chosen when spawning, plus alpha fade
81 // how long the particle should live (note it is also removed if alpha drops to 0)
83 // how much gravity affects this particle (negative makes it fly up!)
85 // how much bounce the particle has when it hits a surface
86 // if negative the particle is removed on impact
88 // if in air this friction is applied
89 // if negative the particle accelerates
91 // if in liquid (water/slime/lava) this friction is applied
92 // if negative the particle accelerates
94 // these offsets are added to the values given to particleeffect(), and
95 // then an ellipsoid-shaped jitter is added as defined by these
96 // (they are the 3 radii)
97 float originoffset[3];
98 float velocityoffset[3];
99 float originjitter[3];
100 float velocityjitter[3];
101 float velocitymultiplier;
102 // an effect can also spawn a dlight
103 float lightradiusstart;
104 float lightradiusfade;
107 qboolean lightshadow;
110 particleeffectinfo_t;
112 #define MAX_PARTICLEEFFECTNAME 256
113 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
115 #define MAX_PARTICLEEFFECTINFO 4096
117 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
119 static int particlepalette[256] =
121 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
122 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
123 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
124 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
125 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
126 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
127 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
128 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
129 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
130 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
131 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
132 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
133 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
134 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
135 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
136 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
137 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
138 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
139 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
140 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
141 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
142 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
143 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
144 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
145 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
146 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
147 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
148 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
149 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
150 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
151 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
152 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
155 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
156 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
157 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
159 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
161 // texture numbers in particle font
162 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
163 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
164 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
165 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
166 static const int tex_rainsplash = 32;
167 static const int tex_particle = 63;
168 static const int tex_bubble = 62;
169 static const int tex_raindrop = 61;
170 static const int tex_beam = 60;
172 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
173 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
174 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
175 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
176 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
177 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
178 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
179 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
180 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
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", "1", "enables decals (bullet holes, blood, etc)"};
191 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
192 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "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_sparks);
428 Cvar_RegisterVariable (&cl_particles_explosions_shell);
429 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
430 Cvar_RegisterVariable (&cl_particles_rain);
431 Cvar_RegisterVariable (&cl_particles_snow);
432 Cvar_RegisterVariable (&cl_particles_smoke);
433 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
434 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
435 Cvar_RegisterVariable (&cl_particles_sparks);
436 Cvar_RegisterVariable (&cl_particles_bubbles);
437 Cvar_RegisterVariable (&cl_decals);
438 Cvar_RegisterVariable (&cl_decals_time);
439 Cvar_RegisterVariable (&cl_decals_fadetime);
442 void CL_Particles_Shutdown (void)
446 // list of all 26 parameters:
447 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
448 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
449 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
450 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
451 // palpha - opacity of particle as 0-255 (can be more than 255)
452 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
453 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
454 // pgravity - how much effect gravity has on the particle (0-1)
455 // 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
456 // px,py,pz - starting origin of particle
457 // pvx,pvy,pvz - starting velocity of particle
458 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
459 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)
464 if (!cl_particles.integer)
466 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
467 if (cl.free_particle >= cl.max_particles)
469 part = &cl.particles[cl.free_particle++];
470 if (cl.num_particles < cl.free_particle)
471 cl.num_particles = cl.free_particle;
472 memset(part, 0, sizeof(*part));
473 part->typeindex = ptypeindex;
474 l2 = (int)lhrandom(0.5, 256.5);
476 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
477 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
478 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
479 part->color[3] = 0xFF;
482 part->sizeincrease = psizeincrease;
483 part->alpha = palpha;
484 part->alphafade = palphafade;
485 part->gravity = pgravity;
486 part->bounce = pbounce;
488 part->org[0] = px + originjitter * v[0];
489 part->org[1] = py + originjitter * v[1];
490 part->org[2] = pz + originjitter * v[2];
491 part->vel[0] = pvx + velocityjitter * v[0];
492 part->vel[1] = pvy + velocityjitter * v[1];
493 part->vel[2] = pvz + velocityjitter * v[2];
495 part->airfriction = pairfriction;
496 part->liquidfriction = pliquidfriction;
497 part->die = cl.time + part->alpha / (part->alphafade ? part->alphafade : 1);
498 part->delayedcollisions = 0;
499 if (part->typeindex == pt_blood)
500 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)
501 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
502 if (part->typeindex == pt_rain)
506 float lifetime = part->die - cl.time;
509 // turn raindrop into simple spark and create delayedspawn splash effect
510 part->typeindex = pt_spark;
512 VectorMA(part->org, lifetime, part->vel, endvec);
513 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
514 part->die = cl.time + lifetime * trace.fraction;
515 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);
518 part2->delayedspawn = part->die;
519 part2->die += part->die - cl.time;
520 for (i = rand() & 7;i < 10;i++)
522 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);
525 part2->delayedspawn = part->die;
526 part2->die += part->die - cl.time;
531 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
533 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
536 VectorMA(part->org, lifetime, part->vel, endvec);
537 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
538 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
543 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
547 if (!cl_decals.integer)
549 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
550 if (cl.free_decal >= cl.max_decals)
552 decal = &cl.decals[cl.free_decal++];
553 if (cl.num_decals < cl.free_decal)
554 cl.num_decals = cl.free_decal;
555 memset(decal, 0, sizeof(*decal));
556 decal->typeindex = pt_decal;
557 decal->texnum = texnum;
558 VectorAdd(org, normal, decal->org);
559 VectorCopy(normal, decal->normal);
561 decal->alpha = alpha;
562 decal->time2 = cl.time;
563 l2 = (int)lhrandom(0.5, 256.5);
565 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
566 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
567 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
568 decal->color[3] = 0xFF;
569 decal->owner = hitent;
572 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
573 decal->ownermodel = cl.entities[decal->owner].render.model;
574 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
575 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
579 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
582 float bestfrac, bestorg[3], bestnormal[3];
584 int besthitent = 0, hitent;
587 for (i = 0;i < 32;i++)
590 VectorMA(org, maxdist, org2, org2);
591 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
592 // take the closest trace result that doesn't end up hitting a NOMARKS
593 // surface (sky for example)
594 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
596 bestfrac = trace.fraction;
598 VectorCopy(trace.endpos, bestorg);
599 VectorCopy(trace.plane.normal, bestnormal);
603 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
606 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
607 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
608 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)
611 matrix4x4_t tempmatrix;
612 VectorLerp(originmins, 0.5, originmaxs, center);
613 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
614 if (effectnameindex == EFFECT_SVC_PARTICLE)
616 if (cl_particles.integer)
618 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
620 CL_ParticleExplosion(center);
621 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
622 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
625 count *= cl_particles_quality.value;
626 for (;count > 0;count--)
628 int k = particlepalette[palettecolor + (rand()&7)];
629 if (cl_particles_quake.integer)
630 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);
631 else if (gamemode == GAME_GOODVSBAD2)
632 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);
634 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);
639 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
640 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
641 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
642 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
643 else if (effectnameindex == EFFECT_TE_SPIKE)
645 if (cl_particles_bulletimpacts.integer)
647 if (cl_particles_quake.integer)
649 if (cl_particles_smoke.integer)
650 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
654 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
655 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
659 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
660 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
662 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
664 if (cl_particles_bulletimpacts.integer)
666 if (cl_particles_quake.integer)
668 if (cl_particles_smoke.integer)
669 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
673 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
674 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*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);
680 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);
682 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
684 if (cl_particles_bulletimpacts.integer)
686 if (cl_particles_quake.integer)
688 if (cl_particles_smoke.integer)
689 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
693 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
694 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
698 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
699 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
701 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
703 if (cl_particles_bulletimpacts.integer)
705 if (cl_particles_quake.integer)
707 if (cl_particles_smoke.integer)
708 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
712 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
713 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
717 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
718 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
719 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);
721 else if (effectnameindex == EFFECT_TE_BLOOD)
723 if (!cl_particles_blood.integer)
725 if (cl_particles_quake.integer)
726 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
729 static double bloodaccumulator = 0;
730 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
731 for (;bloodaccumulator > 0;bloodaccumulator--)
732 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);
735 else if (effectnameindex == EFFECT_TE_SPARK)
736 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
737 else if (effectnameindex == EFFECT_TE_PLASMABURN)
739 // plasma scorch mark
740 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
741 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
742 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
744 else if (effectnameindex == EFFECT_TE_GUNSHOT)
746 if (cl_particles_bulletimpacts.integer)
748 if (cl_particles_quake.integer)
749 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
752 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
753 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
757 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
758 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
760 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
762 if (cl_particles_bulletimpacts.integer)
764 if (cl_particles_quake.integer)
765 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
768 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
769 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
773 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
774 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
775 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);
777 else if (effectnameindex == EFFECT_TE_EXPLOSION)
779 CL_ParticleExplosion(center);
780 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);
782 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
784 CL_ParticleExplosion(center);
785 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);
787 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
789 if (cl_particles_quake.integer)
792 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
795 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);
797 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);
801 CL_ParticleExplosion(center);
802 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);
804 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
805 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);
806 else if (effectnameindex == EFFECT_TE_FLAMEJET)
808 count *= cl_particles_quality.value;
810 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);
812 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
814 float i, j, inc, vel;
817 inc = 8 / cl_particles_quality.value;
818 for (i = -128;i < 128;i += inc)
820 for (j = -128;j < 128;j += inc)
822 dir[0] = j + lhrandom(0, inc);
823 dir[1] = i + lhrandom(0, inc);
825 org[0] = center[0] + dir[0];
826 org[1] = center[1] + dir[1];
827 org[2] = center[2] + lhrandom(0, 64);
828 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
829 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);
833 else if (effectnameindex == EFFECT_TE_TELEPORT)
835 float i, j, k, inc, vel;
838 inc = 8 / cl_particles_quality.value;
839 for (i = -16;i < 16;i += inc)
841 for (j = -16;j < 16;j += inc)
843 for (k = -24;k < 32;k += inc)
845 VectorSet(dir, i*8, j*8, k*8);
846 VectorNormalize(dir);
847 vel = lhrandom(50, 113);
848 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);
852 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);
853 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);
855 else if (effectnameindex == EFFECT_TE_TEI_G3)
856 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);
857 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
859 if (cl_particles_smoke.integer)
861 count *= 0.25f * cl_particles_quality.value;
863 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);
866 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
868 CL_ParticleExplosion(center);
869 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);
871 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
874 if (cl_stainmaps.integer)
875 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
876 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
877 if (cl_particles_smoke.integer)
878 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
879 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);
880 if (cl_particles_sparks.integer)
881 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
882 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);
883 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);
885 else if (effectnameindex == EFFECT_EF_FLAME)
887 count *= 300 * cl_particles_quality.value;
889 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);
890 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);
892 else if (effectnameindex == EFFECT_EF_STARDUST)
894 count *= 200 * cl_particles_quality.value;
896 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);
897 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);
899 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
903 int smoke, blood, bubbles, r, color;
905 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
908 Vector4Set(light, 0, 0, 0, 0);
910 if (effectnameindex == EFFECT_TR_ROCKET)
911 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
912 else if (effectnameindex == EFFECT_TR_VORESPIKE)
914 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
915 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
917 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
919 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
920 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
924 matrix4x4_t tempmatrix;
925 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
926 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);
933 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
936 VectorSubtract(originmaxs, originmins, dir);
937 len = VectorNormalizeLength(dir);
940 dec = -ent->persistent.trail_time;
941 ent->persistent.trail_time += len;
942 if (ent->persistent.trail_time < 0.01f)
945 // if we skip out, leave it reset
946 ent->persistent.trail_time = 0.0f;
951 // advance into this frame to reach the first puff location
952 VectorMA(originmins, dec, dir, pos);
955 smoke = cl_particles.integer && cl_particles_smoke.integer;
956 blood = cl_particles.integer && cl_particles_blood.integer;
957 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
958 qd = 1.0f / cl_particles_quality.value;
965 if (effectnameindex == EFFECT_TR_BLOOD)
967 if (cl_particles_quake.integer)
969 color = particlepalette[67 + (rand()&3)];
970 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);
975 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);
978 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
980 if (cl_particles_quake.integer)
983 color = particlepalette[67 + (rand()&3)];
984 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);
989 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);
995 if (effectnameindex == EFFECT_TR_ROCKET)
997 if (cl_particles_quake.integer)
1000 color = particlepalette[ramp3[r]];
1001 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);
1005 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);
1006 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);
1009 else if (effectnameindex == EFFECT_TR_GRENADE)
1011 if (cl_particles_quake.integer)
1014 color = particlepalette[ramp3[r]];
1015 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);
1019 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);
1022 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1024 if (cl_particles_quake.integer)
1027 color = particlepalette[52 + (rand()&7)];
1028 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);
1029 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);
1031 else if (gamemode == GAME_GOODVSBAD2)
1034 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);
1038 color = particlepalette[20 + (rand()&7)];
1039 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);
1042 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1044 if (cl_particles_quake.integer)
1047 color = particlepalette[230 + (rand()&7)];
1048 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);
1049 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);
1053 color = particlepalette[226 + (rand()&7)];
1054 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);
1057 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1059 if (cl_particles_quake.integer)
1061 color = particlepalette[152 + (rand()&3)];
1062 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);
1064 else if (gamemode == GAME_GOODVSBAD2)
1067 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);
1069 else if (gamemode == GAME_PRYDON)
1072 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);
1075 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);
1077 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1080 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);
1082 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1085 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);
1087 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1088 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);
1092 if (effectnameindex == EFFECT_TR_ROCKET)
1093 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);
1094 else if (effectnameindex == EFFECT_TR_GRENADE)
1095 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);
1097 // advance to next time and position
1100 VectorMA (pos, dec, dir, pos);
1103 ent->persistent.trail_time = len;
1105 else if (developer.integer >= 1)
1106 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1109 // this is also called on point effects with spawndlight = true and
1110 // spawnparticles = true
1111 // it is called CL_ParticleTrail because most code does not want to supply
1112 // these parameters, only trail handling does
1113 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)
1116 qboolean found = false;
1117 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1118 return; // invalid effect index
1119 if (!particleeffectname[effectnameindex][0])
1120 return; // no such effect
1121 VectorLerp(originmins, 0.5, originmaxs, center);
1122 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1124 int effectinfoindex;
1127 particleeffectinfo_t *info;
1129 vec3_t centervelocity;
1135 qboolean underwater;
1136 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1137 VectorLerp(originmins, 0.5, originmaxs, center);
1138 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1139 supercontents = CL_PointSuperContents(center);
1140 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1141 VectorSubtract(originmaxs, originmins, traildir);
1142 traillen = VectorLength(traildir);
1143 VectorNormalize(traildir);
1144 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1146 if (info->effectnameindex == effectnameindex)
1149 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1151 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1154 // spawn a dlight if requested
1155 if (info->lightradiusstart > 0 && spawndlight)
1157 matrix4x4_t tempmatrix;
1158 if (info->trailspacing > 0)
1159 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1161 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1162 if (info->lighttime > 0 && info->lightradiusfade > 0)
1164 // light flash (explosion, etc)
1165 // called when effect starts
1166 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);
1171 // called by CL_LinkNetworkEntity
1172 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1173 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);
1177 if (!spawnparticles)
1182 if (info->tex[1] > info->tex[0])
1184 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1185 tex = min(tex, info->tex[1] - 1);
1187 if (info->particletype == pt_decal)
1188 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]);
1189 else if (info->particletype == pt_beam)
1190 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);
1193 if (!cl_particles.integer)
1195 switch (info->particletype)
1197 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1198 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1199 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1200 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1201 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1202 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1205 VectorCopy(originmins, trailpos);
1206 if (info->trailspacing > 0)
1208 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1209 trailstep = info->trailspacing / cl_particles_quality.value;
1213 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1216 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1217 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1219 if (info->tex[1] > info->tex[0])
1221 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1222 tex = min(tex, info->tex[1] - 1);
1226 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1227 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1228 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1231 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);
1233 VectorMA(trailpos, trailstep, traildir, trailpos);
1240 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1243 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)
1245 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1253 void CL_EntityParticles (const entity_t *ent)
1256 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1257 static vec3_t avelocities[NUMVERTEXNORMALS];
1258 if (!cl_particles.integer) return;
1259 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1261 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1263 if (!avelocities[0][0])
1264 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1265 avelocities[0][i] = lhrandom(0, 2.55);
1267 for (i = 0;i < NUMVERTEXNORMALS;i++)
1269 yaw = cl.time * avelocities[i][0];
1270 pitch = cl.time * avelocities[i][1];
1271 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1272 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1273 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1274 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);
1279 void CL_ReadPointFile_f (void)
1281 vec3_t org, leakorg;
1283 char *pointfile = NULL, *pointfilepos, *t, tchar;
1284 char name[MAX_OSPATH];
1289 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1290 strlcat (name, ".pts", sizeof (name));
1291 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1294 Con_Printf("Could not open %s\n", name);
1298 Con_Printf("Reading %s...\n", name);
1299 VectorClear(leakorg);
1302 pointfilepos = pointfile;
1303 while (*pointfilepos)
1305 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1310 while (*t && *t != '\n' && *t != '\r')
1314 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1320 VectorCopy(org, leakorg);
1323 if (cl.num_particles < cl.max_particles - 3)
1326 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);
1329 Mem_Free(pointfile);
1330 VectorCopy(leakorg, org);
1331 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1333 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);
1334 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);
1335 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);
1340 CL_ParseParticleEffect
1342 Parse an effect out of the server message
1345 void CL_ParseParticleEffect (void)
1348 int i, count, msgcount, color;
1350 MSG_ReadVector(org, cls.protocol);
1351 for (i=0 ; i<3 ; i++)
1352 dir[i] = MSG_ReadChar ();
1353 msgcount = MSG_ReadByte ();
1354 color = MSG_ReadByte ();
1356 if (msgcount == 255)
1361 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1366 CL_ParticleExplosion
1370 void CL_ParticleExplosion (const vec3_t org)
1376 if (cl_stainmaps.integer)
1377 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1378 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1380 if (cl_particles_quake.integer)
1382 for (i = 0;i < 1024;i++)
1388 color = particlepalette[ramp1[r]];
1389 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);
1393 color = particlepalette[ramp2[r]];
1394 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);
1400 i = CL_PointSuperContents(org);
1401 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1403 if (cl_particles.integer && cl_particles_bubbles.integer)
1404 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1405 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);
1409 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1411 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1415 for (k = 0;k < 16;k++)
1418 VectorMA(org, 128, v2, v);
1419 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1420 if (trace.fraction >= 0.1)
1423 VectorSubtract(trace.endpos, org, v2);
1424 VectorScale(v2, 2.0f, v2);
1425 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);
1431 if (cl_particles_explosions_shell.integer)
1432 R_NewExplosion(org);
1437 CL_ParticleExplosion2
1441 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1444 if (!cl_particles.integer) return;
1446 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1448 k = particlepalette[colorStart + (i % colorLength)];
1449 if (cl_particles_quake.integer)
1450 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);
1452 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);
1456 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1458 if (cl_particles_sparks.integer)
1460 sparkcount *= cl_particles_quality.value;
1461 while(sparkcount-- > 0)
1462 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);
1466 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1468 if (cl_particles_smoke.integer)
1470 smokecount *= cl_particles_quality.value;
1471 while(smokecount-- > 0)
1472 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);
1476 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)
1479 if (!cl_particles.integer) return;
1481 count = (int)(count * cl_particles_quality.value);
1484 k = particlepalette[colorbase + (rand()&3)];
1485 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);
1489 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1492 float z, minz, maxz;
1493 if (!cl_particles.integer) return;
1494 if (dir[2] < 0) // falling
1499 minz = z - fabs(dir[2]) * 0.1;
1500 maxz = z + fabs(dir[2]) * 0.1;
1501 minz = bound(mins[2], minz, maxs[2]);
1502 maxz = bound(mins[2], maxz, maxs[2]);
1504 count = (int)(count * cl_particles_quality.value);
1509 if (!cl_particles_rain.integer) break;
1510 count *= 4; // ick, this should be in the mod or maps?
1514 k = particlepalette[colorbase + (rand()&3)];
1515 if (gamemode == GAME_GOODVSBAD2)
1516 CL_NewParticle(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);
1518 CL_NewParticle(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);
1522 if (!cl_particles_snow.integer) break;
1525 k = particlepalette[colorbase + (rand()&3)];
1526 if (gamemode == GAME_GOODVSBAD2)
1527 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);
1529 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);
1533 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1537 #define MAX_PARTICLETEXTURES 64
1538 // particletexture_t is a rectangle in the particlefonttexture
1539 typedef struct particletexture_s
1541 rtexture_t *texture;
1542 float s1, t1, s2, t2;
1546 static rtexturepool_t *particletexturepool;
1547 static rtexture_t *particlefonttexture;
1548 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1550 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1551 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1553 #define PARTICLETEXTURESIZE 64
1554 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1556 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1560 dz = 1 - (dx*dx+dy*dy);
1561 if (dz > 0) // it does hit the sphere
1565 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1566 VectorNormalize(normal);
1567 dot = DotProduct(normal, light);
1568 if (dot > 0.5) // interior reflection
1569 f += ((dot * 2) - 1);
1570 else if (dot < -0.5) // exterior reflection
1571 f += ((dot * -2) - 1);
1573 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1574 VectorNormalize(normal);
1575 dot = DotProduct(normal, light);
1576 if (dot > 0.5) // interior reflection
1577 f += ((dot * 2) - 1);
1578 else if (dot < -0.5) // exterior reflection
1579 f += ((dot * -2) - 1);
1581 f += 16; // just to give it a haze so you can see the outline
1582 f = bound(0, f, 255);
1583 return (unsigned char) f;
1589 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1591 int basex, basey, y;
1592 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1593 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1594 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1595 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1598 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1601 float cx, cy, dx, dy, f, iradius;
1603 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1604 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1605 iradius = 1.0f / radius;
1606 alpha *= (1.0f / 255.0f);
1607 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1609 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1613 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1618 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1619 d[0] += (int)(f * (blue - d[0]));
1620 d[1] += (int)(f * (green - d[1]));
1621 d[2] += (int)(f * (red - d[2]));
1627 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1630 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1632 data[0] = bound(minb, data[0], maxb);
1633 data[1] = bound(ming, data[1], maxg);
1634 data[2] = bound(minr, data[2], maxr);
1638 void particletextureinvert(unsigned char *data)
1641 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1643 data[0] = 255 - data[0];
1644 data[1] = 255 - data[1];
1645 data[2] = 255 - data[2];
1649 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1650 static void R_InitBloodTextures (unsigned char *particletexturedata)
1653 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1656 for (i = 0;i < 8;i++)
1658 memset(&data[0][0][0], 255, sizeof(data));
1659 for (k = 0;k < 24;k++)
1660 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1661 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1662 particletextureinvert(&data[0][0][0]);
1663 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1667 for (i = 0;i < 8;i++)
1669 memset(&data[0][0][0], 255, sizeof(data));
1671 for (j = 1;j < 10;j++)
1672 for (k = min(j, m - 1);k < m;k++)
1673 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1674 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1675 particletextureinvert(&data[0][0][0]);
1676 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1681 //uncomment this to make engine save out particle font to a tga file when run
1682 //#define DUMPPARTICLEFONT
1684 static void R_InitParticleTexture (void)
1686 int x, y, d, i, k, m;
1690 // a note: decals need to modulate (multiply) the background color to
1691 // properly darken it (stain), and they need to be able to alpha fade,
1692 // this is a very difficult challenge because it means fading to white
1693 // (no change to background) rather than black (darkening everything
1694 // behind the whole decal polygon), and to accomplish this the texture is
1695 // inverted (dark red blood on white background becomes brilliant cyan
1696 // and white on black background) so we can alpha fade it to black, then
1697 // we invert it again during the blendfunc to make it work...
1699 #ifndef DUMPPARTICLEFONT
1700 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1701 if (!particlefonttexture)
1704 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1705 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1706 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1709 for (i = 0;i < 8;i++)
1711 memset(&data[0][0][0], 255, sizeof(data));
1714 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1716 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1717 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1719 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1721 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1722 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1724 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1725 d = (noise2[y][x] - 128) * 3 + 192;
1727 d = (int)(d * (1-(dx*dx+dy*dy)));
1728 d = (d * noise1[y][x]) >> 7;
1729 d = bound(0, d, 255);
1730 data[y][x][3] = (unsigned char) d;
1737 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1741 memset(&data[0][0][0], 255, sizeof(data));
1742 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1744 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1745 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1747 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1748 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1749 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1752 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1755 memset(&data[0][0][0], 255, sizeof(data));
1756 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1758 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1759 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1761 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1762 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1763 d = bound(0, d, 255);
1764 data[y][x][3] = (unsigned char) d;
1767 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1770 memset(&data[0][0][0], 255, sizeof(data));
1771 light[0] = 1;light[1] = 1;light[2] = 1;
1772 VectorNormalize(light);
1773 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1775 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1776 // stretch upper half of bubble by +50% and shrink lower half by -50%
1777 // (this gives an elongated teardrop shape)
1779 dy = (dy - 0.5f) * 2.0f;
1781 dy = (dy - 0.5f) / 1.5f;
1782 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1784 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1785 // shrink bubble width to half
1787 data[y][x][3] = shadebubble(dx, dy, light);
1790 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1793 memset(&data[0][0][0], 255, sizeof(data));
1794 light[0] = 1;light[1] = 1;light[2] = 1;
1795 VectorNormalize(light);
1796 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1798 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1799 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1801 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1802 data[y][x][3] = shadebubble(dx, dy, light);
1805 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1807 // Blood particles and blood decals
1808 R_InitBloodTextures (particletexturedata);
1811 for (i = 0;i < 8;i++)
1813 memset(&data[0][0][0], 255, sizeof(data));
1814 for (k = 0;k < 12;k++)
1815 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1816 for (k = 0;k < 3;k++)
1817 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1818 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1819 particletextureinvert(&data[0][0][0]);
1820 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1823 #ifdef DUMPPARTICLEFONT
1824 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1827 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1829 Mem_Free(particletexturedata);
1831 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1833 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1834 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1835 particletexture[i].texture = particlefonttexture;
1836 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1837 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1838 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1839 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1842 #ifndef DUMPPARTICLEFONT
1843 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE, true);
1844 if (!particletexture[tex_beam].texture)
1847 unsigned char noise3[64][64], data2[64][16][4];
1849 fractalnoise(&noise3[0][0], 64, 4);
1851 for (y = 0;y < 64;y++)
1853 dy = (y - 0.5f*64) / (64*0.5f-1);
1854 for (x = 0;x < 16;x++)
1856 dx = (x - 0.5f*16) / (16*0.5f-2);
1857 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1858 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1859 data2[y][x][3] = 255;
1863 #ifdef DUMPPARTICLEFONT
1864 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1866 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_PRECACHE, NULL);
1868 particletexture[tex_beam].s1 = 0;
1869 particletexture[tex_beam].t1 = 0;
1870 particletexture[tex_beam].s2 = 1;
1871 particletexture[tex_beam].t2 = 1;
1874 static void r_part_start(void)
1876 particletexturepool = R_AllocTexturePool();
1877 R_InitParticleTexture ();
1878 CL_Particles_LoadEffectInfo();
1881 static void r_part_shutdown(void)
1883 R_FreeTexturePool(&particletexturepool);
1886 static void r_part_newmap(void)
1890 #define BATCHSIZE 256
1891 int particle_element3i[BATCHSIZE*6];
1893 void R_Particles_Init (void)
1896 for (i = 0;i < BATCHSIZE;i++)
1898 particle_element3i[i*6+0] = i*4+0;
1899 particle_element3i[i*6+1] = i*4+1;
1900 particle_element3i[i*6+2] = i*4+2;
1901 particle_element3i[i*6+3] = i*4+0;
1902 particle_element3i[i*6+4] = i*4+2;
1903 particle_element3i[i*6+5] = i*4+3;
1906 Cvar_RegisterVariable(&r_drawparticles);
1907 Cvar_RegisterVariable(&r_drawdecals);
1908 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
1911 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
1913 int surfacelistindex;
1915 float *v3f, *t2f, *c4f;
1916 particletexture_t *tex;
1917 float right[3], up[3], size, ca;
1918 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
1919 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
1921 r_refdef.stats.decals += numsurfaces;
1922 R_Mesh_Matrix(&identitymatrix);
1923 R_Mesh_ResetTextureState();
1924 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
1925 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
1926 R_Mesh_ColorPointer(particle_color4f, 0, 0);
1927 GL_DepthMask(false);
1928 GL_DepthRange(0, 1);
1929 GL_PolygonOffset(0, 0);
1931 GL_CullFace(GL_NONE);
1933 // generate all the vertices at once
1934 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
1936 d = cl.decals + surfacelist[surfacelistindex];
1939 c4f = particle_color4f + 16*surfacelistindex;
1940 ca = d->alpha * alphascale;
1941 if (r_refdef.fogenabled)
1942 ca *= FogPoint_World(d->org);
1943 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
1944 Vector4Copy(c4f, c4f + 4);
1945 Vector4Copy(c4f, c4f + 8);
1946 Vector4Copy(c4f, c4f + 12);
1948 // calculate vertex positions
1949 size = d->size * cl_particles_size.value;
1950 VectorVectors(d->normal, right, up);
1951 VectorScale(right, size, right);
1952 VectorScale(up, size, up);
1953 v3f = particle_vertex3f + 12*surfacelistindex;
1954 v3f[ 0] = d->org[0] - right[0] - up[0];
1955 v3f[ 1] = d->org[1] - right[1] - up[1];
1956 v3f[ 2] = d->org[2] - right[2] - up[2];
1957 v3f[ 3] = d->org[0] - right[0] + up[0];
1958 v3f[ 4] = d->org[1] - right[1] + up[1];
1959 v3f[ 5] = d->org[2] - right[2] + up[2];
1960 v3f[ 6] = d->org[0] + right[0] + up[0];
1961 v3f[ 7] = d->org[1] + right[1] + up[1];
1962 v3f[ 8] = d->org[2] + right[2] + up[2];
1963 v3f[ 9] = d->org[0] + right[0] - up[0];
1964 v3f[10] = d->org[1] + right[1] - up[1];
1965 v3f[11] = d->org[2] + right[2] - up[2];
1967 // calculate texcoords
1968 tex = &particletexture[d->texnum];
1969 t2f = particle_texcoord2f + 8*surfacelistindex;
1970 t2f[0] = tex->s1;t2f[1] = tex->t2;
1971 t2f[2] = tex->s1;t2f[3] = tex->t1;
1972 t2f[4] = tex->s2;t2f[5] = tex->t1;
1973 t2f[6] = tex->s2;t2f[7] = tex->t2;
1976 // now render the decals all at once
1977 // (this assumes they all use one particle font texture!)
1978 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1979 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
1980 GL_LockArrays(0, numsurfaces*4);
1981 R_Mesh_Draw(0, numsurfaces * 4, numsurfaces * 2, particle_element3i, 0, 0);
1982 GL_LockArrays(0, 0);
1985 void R_DrawDecals (void)
1992 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
1993 cl.decals_updatetime += frametime;
1995 // LordHavoc: early out conditions
1996 if ((!cl.num_decals) || (!r_drawdecals.integer))
1999 decalfade = frametime * 256 / cl_decals_fadetime.value;
2001 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2003 if (!decal->typeindex)
2006 if (cl.time > decal->time2 + cl_decals_time.value)
2008 decal->alpha -= decalfade;
2009 if (decal->alpha <= 0)
2015 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2017 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2018 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2023 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2026 decal->typeindex = 0;
2027 if (cl.free_decal > i)
2031 // reduce cl.num_decals if possible
2032 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2036 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2038 int surfacelistindex;
2039 int batchstart, batchcount;
2040 const particle_t *p;
2042 rtexture_t *texture;
2043 float *v3f, *t2f, *c4f;
2044 particletexture_t *tex;
2045 float up2[3], v[3], right[3], up[3], fog, ifog, size;
2046 float ambient[3], diffuse[3], diffusenormal[3];
2047 vec4_t colormultiplier;
2048 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2050 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));
2052 r_refdef.stats.particles += numsurfaces;
2053 R_Mesh_Matrix(&identitymatrix);
2054 R_Mesh_ResetTextureState();
2055 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2056 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2057 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2058 GL_DepthMask(false);
2059 GL_DepthRange(0, 1);
2060 GL_PolygonOffset(0, 0);
2062 GL_CullFace(GL_NONE);
2064 // first generate all the vertices at once
2065 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2067 p = cl.particles + surfacelist[surfacelistindex];
2069 blendmode = particletype[p->typeindex].blendmode;
2071 c4f[0] = p->color[0] * colormultiplier[0];
2072 c4f[1] = p->color[1] * colormultiplier[1];
2073 c4f[2] = p->color[2] * colormultiplier[2];
2074 c4f[3] = p->alpha * colormultiplier[3];
2079 // additive and modulate can just fade out in fog (this is correct)
2080 if (r_refdef.fogenabled)
2081 c4f[3] *= FogPoint_World(p->org);
2082 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2089 // note: lighting is not cheap!
2090 if (particletype[p->typeindex].lighting)
2092 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2093 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2094 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2095 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2097 // mix in the fog color
2098 if (r_refdef.fogenabled)
2100 fog = FogPoint_World(p->org);
2102 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2103 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2104 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2108 // copy the color into the other three vertices
2109 Vector4Copy(c4f, c4f + 4);
2110 Vector4Copy(c4f, c4f + 8);
2111 Vector4Copy(c4f, c4f + 12);
2113 size = p->size * cl_particles_size.value;
2114 tex = &particletexture[p->texnum];
2115 switch(particletype[p->typeindex].orientation)
2117 case PARTICLE_BILLBOARD:
2118 VectorScale(r_refdef.view.left, -size, right);
2119 VectorScale(r_refdef.view.up, size, up);
2120 v3f[ 0] = p->org[0] - right[0] - up[0];
2121 v3f[ 1] = p->org[1] - right[1] - up[1];
2122 v3f[ 2] = p->org[2] - right[2] - up[2];
2123 v3f[ 3] = p->org[0] - right[0] + up[0];
2124 v3f[ 4] = p->org[1] - right[1] + up[1];
2125 v3f[ 5] = p->org[2] - right[2] + up[2];
2126 v3f[ 6] = p->org[0] + right[0] + up[0];
2127 v3f[ 7] = p->org[1] + right[1] + up[1];
2128 v3f[ 8] = p->org[2] + right[2] + up[2];
2129 v3f[ 9] = p->org[0] + right[0] - up[0];
2130 v3f[10] = p->org[1] + right[1] - up[1];
2131 v3f[11] = p->org[2] + right[2] - up[2];
2132 t2f[0] = tex->s1;t2f[1] = tex->t2;
2133 t2f[2] = tex->s1;t2f[3] = tex->t1;
2134 t2f[4] = tex->s2;t2f[5] = tex->t1;
2135 t2f[6] = tex->s2;t2f[7] = tex->t2;
2137 case PARTICLE_ORIENTED_DOUBLESIDED:
2138 VectorVectors(p->vel, right, up);
2139 VectorScale(right, size, right);
2140 VectorScale(up, size, up);
2141 v3f[ 0] = p->org[0] - right[0] - up[0];
2142 v3f[ 1] = p->org[1] - right[1] - up[1];
2143 v3f[ 2] = p->org[2] - right[2] - up[2];
2144 v3f[ 3] = p->org[0] - right[0] + up[0];
2145 v3f[ 4] = p->org[1] - right[1] + up[1];
2146 v3f[ 5] = p->org[2] - right[2] + up[2];
2147 v3f[ 6] = p->org[0] + right[0] + up[0];
2148 v3f[ 7] = p->org[1] + right[1] + up[1];
2149 v3f[ 8] = p->org[2] + right[2] + up[2];
2150 v3f[ 9] = p->org[0] + right[0] - up[0];
2151 v3f[10] = p->org[1] + right[1] - up[1];
2152 v3f[11] = p->org[2] + right[2] - up[2];
2153 t2f[0] = tex->s1;t2f[1] = tex->t2;
2154 t2f[2] = tex->s1;t2f[3] = tex->t1;
2155 t2f[4] = tex->s2;t2f[5] = tex->t1;
2156 t2f[6] = tex->s2;t2f[7] = tex->t2;
2158 case PARTICLE_SPARK:
2159 VectorMA(p->org, -0.02, p->vel, v);
2160 VectorMA(p->org, 0.02, p->vel, up2);
2161 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2162 t2f[0] = tex->s1;t2f[1] = tex->t2;
2163 t2f[2] = tex->s1;t2f[3] = tex->t1;
2164 t2f[4] = tex->s2;t2f[5] = tex->t1;
2165 t2f[6] = tex->s2;t2f[7] = tex->t2;
2168 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2169 VectorSubtract(p->vel, p->org, up);
2170 VectorNormalize(up);
2171 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f);
2172 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2173 t2f[0] = 1;t2f[1] = v[0];
2174 t2f[2] = 0;t2f[3] = v[0];
2175 t2f[4] = 0;t2f[5] = v[1];
2176 t2f[6] = 1;t2f[7] = v[1];
2181 // now render batches of particles based on blendmode and texture
2184 GL_LockArrays(0, numsurfaces*4);
2187 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2189 p = cl.particles + surfacelist[surfacelistindex];
2191 if (blendmode != particletype[p->typeindex].blendmode)
2193 blendmode = particletype[p->typeindex].blendmode;
2197 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2200 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2203 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2207 if (texture != particletexture[p->texnum].texture)
2209 texture = particletexture[p->texnum].texture;
2210 R_Mesh_TexBind(0, R_GetTexture(texture));
2213 // iterate until we find a change in settings
2214 batchstart = surfacelistindex++;
2215 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2217 p = cl.particles + surfacelist[surfacelistindex];
2218 if (blendmode != particletype[p->typeindex].blendmode || texture != particletexture[p->texnum].texture)
2222 batchcount = surfacelistindex - batchstart;
2223 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2225 GL_LockArrays(0, 0);
2228 void R_DrawParticles (void)
2230 int i, j, a, content;
2231 float minparticledist;
2233 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2238 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2239 cl.particles_updatetime += frametime;
2241 // LordHavoc: early out conditions
2242 if ((!cl.num_particles) || (!r_drawparticles.integer))
2245 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2246 gravity = frametime * cl.movevars_gravity;
2247 dvel = 1+4*frametime;
2248 decalfade = frametime * 255 / cl_decals_fadetime.value;
2249 update = frametime > 0;
2252 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2259 if (p->delayedspawn > cl.time)
2261 p->delayedspawn = 0;
2265 p->size += p->sizeincrease * frametime;
2266 p->alpha -= p->alphafade * frametime;
2268 if (p->alpha <= 0 || p->die <= cl.time)
2271 if (particletype[p->typeindex].orientation != PARTICLE_BEAM && frametime > 0)
2273 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2275 if (p->typeindex == pt_blood)
2276 p->size += frametime * 8;
2278 p->vel[2] -= p->gravity * gravity;
2279 f = 1.0f - min(p->liquidfriction * frametime, 1);
2280 VectorScale(p->vel, f, p->vel);
2284 p->vel[2] -= p->gravity * gravity;
2287 f = 1.0f - min(p->airfriction * frametime, 1);
2288 VectorScale(p->vel, f, p->vel);
2292 VectorCopy(p->org, oldorg);
2293 VectorMA(p->org, frametime, p->vel, p->org);
2294 if (p->bounce && cl.time >= p->delayedcollisions)
2296 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);
2297 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2298 // or if the trace hit something flagged as NOIMPACT
2299 // then remove the particle
2300 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2302 VectorCopy(trace.endpos, p->org);
2303 // react if the particle hit something
2304 if (trace.fraction < 1)
2306 VectorCopy(trace.endpos, p->org);
2307 if (p->typeindex == pt_rain)
2309 // raindrop - splash on solid/water/slime/lava
2311 // convert from a raindrop particle to a rainsplash decal
2312 VectorCopy(trace.plane.normal, p->vel);
2313 VectorAdd(p->org, p->vel, p->org);
2314 p->typeindex = pt_raindecal;
2315 p->texnum = tex_rainsplash;
2317 p->alphafade = p->alpha / 0.4;
2320 p->liquidfriction = 0;
2323 p->sizeincrease = p->size * 20;
2324 count = (int)lhrandom(1, 10);
2326 CL_NewParticle(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);
2329 else if (p->typeindex == pt_blood)
2331 // blood - splash on solid
2332 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2334 if (cl_stainmaps.integer)
2335 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)));
2336 if (cl_decals.integer)
2338 // create a decal for the blood splat
2339 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);
2343 else if (p->bounce < 0)
2345 // bounce -1 means remove on impact
2350 // anything else - bounce off solid
2351 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2352 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2353 if (DotProduct(p->vel, p->vel) < 0.03)
2354 VectorClear(p->vel);
2360 if (p->typeindex != pt_static)
2362 switch (p->typeindex)
2364 case pt_entityparticle:
2365 // particle that removes itself after one rendered frame
2372 a = CL_PointSuperContents(p->org);
2373 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2377 a = CL_PointSuperContents(p->org);
2378 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2382 a = CL_PointSuperContents(p->org);
2383 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2387 if (cl.time > p->time2)
2390 p->time2 = cl.time + (rand() & 3) * 0.1;
2391 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2392 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2394 a = CL_PointSuperContents(p->org);
2395 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2403 else if (p->delayedspawn)
2406 // don't render particles too close to the view (they chew fillrate)
2407 // also don't render particles behind the view (useless)
2408 // further checks to cull to the frustum would be too slow here
2409 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist || particletype[p->typeindex].orientation == PARTICLE_BEAM)
2410 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2415 if (cl.free_particle > i)
2416 cl.free_particle = i;
2419 // reduce cl.num_particles if possible
2420 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)