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 (part->type == particletype + pt_blood)
499 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)
500 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
501 if (part->type == particletype + pt_rain)
505 float lifetime = part->die - cl.time;
508 // turn raindrop into simple spark and create delayedspawn splash effect
509 part->type = particletype + pt_spark;
511 VectorMA(part->org, lifetime, part->vel, endvec);
512 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);
513 part->die = cl.time + lifetime * trace.fraction;
514 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);
517 part2->delayedspawn = part->die;
518 part2->die += part->die - cl.time;
519 for (i = rand() & 7;i < 10;i++)
521 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);
524 part2->delayedspawn = part->die;
525 part2->die += part->die - cl.time;
530 else if (part->bounce != 0 && part->gravity == 0)
532 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
535 VectorMA(part->org, lifetime, part->vel, endvec);
536 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);
537 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
542 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
545 if (!cl_decals.integer)
547 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);
552 p->ownermodel = cl.entities[p->owner].render.model;
553 VectorAdd(org, normal, p->org);
554 VectorCopy(normal, p->vel);
555 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
556 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
557 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
561 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
564 float bestfrac, bestorg[3], bestnormal[3];
566 int besthitent = 0, hitent;
569 for (i = 0;i < 32;i++)
572 VectorMA(org, maxdist, org2, org2);
573 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
574 // take the closest trace result that doesn't end up hitting a NOMARKS
575 // surface (sky for example)
576 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
578 bestfrac = trace.fraction;
580 VectorCopy(trace.endpos, bestorg);
581 VectorCopy(trace.plane.normal, bestnormal);
585 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
588 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
589 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
590 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)
593 matrix4x4_t tempmatrix;
594 VectorLerp(originmins, 0.5, originmaxs, center);
595 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
596 if (effectnameindex == EFFECT_SVC_PARTICLE)
598 if (cl_particles.integer)
600 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
602 CL_ParticleExplosion(center);
603 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
604 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
607 count *= cl_particles_quality.value;
608 for (;count > 0;count--)
610 int k = particlepalette[palettecolor + (rand()&7)];
611 if (cl_particles_quake.integer)
612 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);
613 else if (gamemode == GAME_GOODVSBAD2)
614 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);
616 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);
621 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
622 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
623 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
624 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
625 else if (effectnameindex == EFFECT_TE_SPIKE)
627 if (cl_particles_bulletimpacts.integer)
629 if (cl_particles_quake.integer)
631 if (cl_particles_smoke.integer)
632 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
636 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
637 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
641 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
642 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
644 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
646 if (cl_particles_bulletimpacts.integer)
648 if (cl_particles_quake.integer)
650 if (cl_particles_smoke.integer)
651 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
655 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
656 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
660 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
661 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
662 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);
664 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
666 if (cl_particles_bulletimpacts.integer)
668 if (cl_particles_quake.integer)
670 if (cl_particles_smoke.integer)
671 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
675 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
676 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
680 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
681 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
683 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
685 if (cl_particles_bulletimpacts.integer)
687 if (cl_particles_quake.integer)
689 if (cl_particles_smoke.integer)
690 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
694 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
695 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
699 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
700 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
701 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);
703 else if (effectnameindex == EFFECT_TE_BLOOD)
705 if (!cl_particles_blood.integer)
707 if (cl_particles_quake.integer)
708 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
711 static double bloodaccumulator = 0;
712 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
713 for (;bloodaccumulator > 0;bloodaccumulator--)
714 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);
717 else if (effectnameindex == EFFECT_TE_SPARK)
718 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
719 else if (effectnameindex == EFFECT_TE_PLASMABURN)
721 // plasma scorch mark
722 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
723 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
724 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
726 else if (effectnameindex == EFFECT_TE_GUNSHOT)
728 if (cl_particles_bulletimpacts.integer)
730 if (cl_particles_quake.integer)
731 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
734 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
735 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
739 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
740 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
742 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
744 if (cl_particles_bulletimpacts.integer)
746 if (cl_particles_quake.integer)
747 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
750 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
751 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
755 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
756 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
757 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
759 else if (effectnameindex == EFFECT_TE_EXPLOSION)
761 CL_ParticleExplosion(center);
762 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);
764 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
766 CL_ParticleExplosion(center);
767 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);
769 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
771 if (cl_particles_quake.integer)
774 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
777 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);
779 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);
783 CL_ParticleExplosion(center);
784 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);
786 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
787 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);
788 else if (effectnameindex == EFFECT_TE_FLAMEJET)
790 count *= cl_particles_quality.value;
792 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);
794 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
796 float i, j, inc, vel;
799 inc = 8 / cl_particles_quality.value;
800 for (i = -128;i < 128;i += inc)
802 for (j = -128;j < 128;j += inc)
804 dir[0] = j + lhrandom(0, inc);
805 dir[1] = i + lhrandom(0, inc);
807 org[0] = center[0] + dir[0];
808 org[1] = center[1] + dir[1];
809 org[2] = center[2] + lhrandom(0, 64);
810 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
811 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);
815 else if (effectnameindex == EFFECT_TE_TELEPORT)
817 float i, j, k, inc, vel;
820 inc = 8 / cl_particles_quality.value;
821 for (i = -16;i < 16;i += inc)
823 for (j = -16;j < 16;j += inc)
825 for (k = -24;k < 32;k += inc)
827 VectorSet(dir, i*8, j*8, k*8);
828 VectorNormalize(dir);
829 vel = lhrandom(50, 113);
830 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);
834 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);
835 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);
837 else if (effectnameindex == EFFECT_TE_TEI_G3)
838 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);
839 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
841 if (cl_particles_smoke.integer)
843 count *= 0.25f * cl_particles_quality.value;
845 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);
848 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
850 CL_ParticleExplosion(center);
851 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);
853 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
856 if (cl_stainmaps.integer)
857 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
858 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
859 if (cl_particles_smoke.integer)
860 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
861 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);
862 if (cl_particles_sparks.integer)
863 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
864 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);
865 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);
867 else if (effectnameindex == EFFECT_EF_FLAME)
869 count *= 300 * cl_particles_quality.value;
871 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);
872 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);
874 else if (effectnameindex == EFFECT_EF_STARDUST)
876 count *= 200 * cl_particles_quality.value;
878 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);
879 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);
881 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
885 int smoke, blood, bubbles, r, color;
887 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
890 Vector4Set(light, 0, 0, 0, 0);
892 if (effectnameindex == EFFECT_TR_ROCKET)
893 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
894 else if (effectnameindex == EFFECT_TR_VORESPIKE)
896 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
897 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
899 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
901 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
902 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
906 matrix4x4_t tempmatrix;
907 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
908 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);
915 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
918 VectorSubtract(originmaxs, originmins, dir);
919 len = VectorNormalizeLength(dir);
922 dec = -ent->persistent.trail_time;
923 ent->persistent.trail_time += len;
924 if (ent->persistent.trail_time < 0.01f)
927 // if we skip out, leave it reset
928 ent->persistent.trail_time = 0.0f;
933 // advance into this frame to reach the first puff location
934 VectorMA(originmins, dec, dir, pos);
937 smoke = cl_particles.integer && cl_particles_smoke.integer;
938 blood = cl_particles.integer && cl_particles_blood.integer;
939 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
940 qd = 1.0f / cl_particles_quality.value;
947 if (effectnameindex == EFFECT_TR_BLOOD)
949 if (cl_particles_quake.integer)
951 color = particlepalette[67 + (rand()&3)];
952 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);
957 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);
960 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
962 if (cl_particles_quake.integer)
965 color = particlepalette[67 + (rand()&3)];
966 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);
971 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);
977 if (effectnameindex == EFFECT_TR_ROCKET)
979 if (cl_particles_quake.integer)
982 color = particlepalette[ramp3[r]];
983 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);
987 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);
988 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);
991 else if (effectnameindex == EFFECT_TR_GRENADE)
993 if (cl_particles_quake.integer)
996 color = particlepalette[ramp3[r]];
997 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);
1001 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);
1004 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1006 if (cl_particles_quake.integer)
1009 color = particlepalette[52 + (rand()&7)];
1010 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 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);
1013 else if (gamemode == GAME_GOODVSBAD2)
1016 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);
1020 color = particlepalette[20 + (rand()&7)];
1021 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);
1024 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1026 if (cl_particles_quake.integer)
1029 color = particlepalette[230 + (rand()&7)];
1030 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);
1031 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);
1035 color = particlepalette[226 + (rand()&7)];
1036 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);
1039 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1041 if (cl_particles_quake.integer)
1043 color = particlepalette[152 + (rand()&3)];
1044 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);
1046 else if (gamemode == GAME_GOODVSBAD2)
1049 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);
1051 else if (gamemode == GAME_PRYDON)
1054 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);
1057 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);
1059 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1062 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);
1064 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1067 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);
1069 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1070 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);
1074 if (effectnameindex == EFFECT_TR_ROCKET)
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);
1076 else if (effectnameindex == EFFECT_TR_GRENADE)
1077 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);
1079 // advance to next time and position
1082 VectorMA (pos, dec, dir, pos);
1085 ent->persistent.trail_time = len;
1087 else if (developer.integer >= 1)
1088 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1091 // this is also called on point effects with spawndlight = true and
1092 // spawnparticles = true
1093 // it is called CL_ParticleTrail because most code does not want to supply
1094 // these parameters, only trail handling does
1095 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)
1098 qboolean found = false;
1099 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1100 return; // invalid effect index
1101 if (!particleeffectname[effectnameindex][0])
1102 return; // no such effect
1103 VectorLerp(originmins, 0.5, originmaxs, center);
1104 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1106 int effectinfoindex;
1109 particleeffectinfo_t *info;
1111 vec3_t centervelocity;
1117 qboolean underwater;
1118 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1119 VectorLerp(originmins, 0.5, originmaxs, center);
1120 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1121 supercontents = CL_PointSuperContents(center);
1122 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1123 VectorSubtract(originmaxs, originmins, traildir);
1124 traillen = VectorLength(traildir);
1125 VectorNormalize(traildir);
1126 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1128 if (info->effectnameindex == effectnameindex)
1131 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1133 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1136 // spawn a dlight if requested
1137 if (info->lightradiusstart > 0 && spawndlight)
1139 matrix4x4_t tempmatrix;
1140 if (info->trailspacing > 0)
1141 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1143 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1144 if (info->lighttime > 0 && info->lightradiusfade > 0)
1146 // light flash (explosion, etc)
1147 // called when effect starts
1148 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);
1153 // called by CL_LinkNetworkEntity
1154 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1155 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);
1159 if (!spawnparticles)
1164 if (info->tex[1] > info->tex[0])
1166 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1167 tex = min(tex, info->tex[1] - 1);
1169 if (info->particletype == pt_decal)
1170 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]);
1171 else if (info->particletype == pt_beam)
1172 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);
1175 if (!cl_particles.integer)
1177 switch (info->particletype)
1179 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1180 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1181 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1182 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1183 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1184 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1187 VectorCopy(originmins, trailpos);
1188 if (info->trailspacing > 0)
1190 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1191 trailstep = info->trailspacing / cl_particles_quality.value;
1195 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1198 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1199 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1201 if (info->tex[1] > info->tex[0])
1203 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1204 tex = min(tex, info->tex[1] - 1);
1208 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1209 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1210 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1213 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);
1215 VectorMA(trailpos, trailstep, traildir, trailpos);
1222 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1225 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)
1227 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1235 void CL_EntityParticles (const entity_t *ent)
1238 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1239 static vec3_t avelocities[NUMVERTEXNORMALS];
1240 if (!cl_particles.integer) return;
1242 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1244 if (!avelocities[0][0])
1245 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1246 avelocities[0][i] = lhrandom(0, 2.55);
1248 for (i = 0;i < NUMVERTEXNORMALS;i++)
1250 yaw = cl.time * avelocities[i][0];
1251 pitch = cl.time * avelocities[i][1];
1252 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1253 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1254 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1255 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);
1260 void CL_ReadPointFile_f (void)
1262 vec3_t org, leakorg;
1264 char *pointfile = NULL, *pointfilepos, *t, tchar;
1265 char name[MAX_OSPATH];
1270 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1271 strlcat (name, ".pts", sizeof (name));
1272 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1275 Con_Printf("Could not open %s\n", name);
1279 Con_Printf("Reading %s...\n", name);
1280 VectorClear(leakorg);
1283 pointfilepos = pointfile;
1284 while (*pointfilepos)
1286 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1291 while (*t && *t != '\n' && *t != '\r')
1295 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1301 VectorCopy(org, leakorg);
1304 if (cl.num_particles < cl.max_particles - 3)
1307 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);
1310 Mem_Free(pointfile);
1311 VectorCopy(leakorg, org);
1312 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1314 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);
1315 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);
1316 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);
1321 CL_ParseParticleEffect
1323 Parse an effect out of the server message
1326 void CL_ParseParticleEffect (void)
1329 int i, count, msgcount, color;
1331 MSG_ReadVector(org, cls.protocol);
1332 for (i=0 ; i<3 ; i++)
1333 dir[i] = MSG_ReadChar ();
1334 msgcount = MSG_ReadByte ();
1335 color = MSG_ReadByte ();
1337 if (msgcount == 255)
1342 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1347 CL_ParticleExplosion
1351 void CL_ParticleExplosion (const vec3_t org)
1357 if (cl_stainmaps.integer)
1358 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1359 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1361 if (cl_particles_quake.integer)
1363 for (i = 0;i < 1024;i++)
1369 color = particlepalette[ramp1[r]];
1370 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);
1374 color = particlepalette[ramp2[r]];
1375 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);
1381 i = CL_PointSuperContents(org);
1382 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1384 if (cl_particles.integer && cl_particles_bubbles.integer)
1385 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1386 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);
1390 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1392 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1394 for (i = 0;i < 32;i++)
1398 for (k = 0;k < 16;k++)
1400 v[0] = org[0] + lhrandom(-48, 48);
1401 v[1] = org[1] + lhrandom(-48, 48);
1402 v[2] = org[2] + lhrandom(-48, 48);
1403 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1404 if (trace.fraction >= 0.1)
1407 VectorSubtract(trace.endpos, org, v2);
1408 VectorScale(v2, 2.0f, v2);
1409 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);
1413 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1415 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1419 for (k = 0;k < 16;k++)
1422 VectorMA(org, 128, v2, v);
1423 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1424 if (trace.fraction >= 0.1)
1427 VectorSubtract(trace.endpos, org, v2);
1428 VectorScale(v2, 2.0f, v2);
1429 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);
1435 if (cl_particles_explosions_shell.integer)
1436 R_NewExplosion(org);
1441 CL_ParticleExplosion2
1445 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1448 if (!cl_particles.integer) return;
1450 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1452 k = particlepalette[colorStart + (i % colorLength)];
1453 if (cl_particles_quake.integer)
1454 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);
1456 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);
1460 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1462 if (cl_particles_sparks.integer)
1464 sparkcount *= cl_particles_quality.value;
1465 while(sparkcount-- > 0)
1466 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);
1470 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1472 if (cl_particles_smoke.integer)
1474 smokecount *= cl_particles_quality.value;
1475 while(smokecount-- > 0)
1476 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);
1480 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)
1483 if (!cl_particles.integer) return;
1485 count = (int)(count * cl_particles_quality.value);
1488 k = particlepalette[colorbase + (rand()&3)];
1489 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);
1493 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1496 float z, minz, maxz;
1498 if (!cl_particles.integer) return;
1499 if (dir[2] < 0) // falling
1504 minz = z - fabs(dir[2]) * 0.1;
1505 maxz = z + fabs(dir[2]) * 0.1;
1506 minz = bound(mins[2], minz, maxs[2]);
1507 maxz = bound(mins[2], maxz, maxs[2]);
1509 count = (int)(count * cl_particles_quality.value);
1514 if (!cl_particles_rain.integer) break;
1515 count *= 4; // ick, this should be in the mod or maps?
1519 k = particlepalette[colorbase + (rand()&3)];
1520 if (gamemode == GAME_GOODVSBAD2)
1521 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);
1523 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);
1527 if (!cl_particles_snow.integer) break;
1530 k = particlepalette[colorbase + (rand()&3)];
1531 if (gamemode == GAME_GOODVSBAD2)
1532 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);
1534 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);
1536 VectorCopy(p->vel, p->relativedirection);
1540 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1549 void CL_MoveParticles (void)
1552 int i, maxparticle, j, a, content;
1553 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
1554 particletype_t *decaltype, *bloodtype;
1558 // LordHavoc: early out condition
1559 if (!cl.num_particles)
1561 cl.free_particle = 0;
1565 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1566 gravity = frametime * cl.movevars_gravity;
1567 dvel = 1+4*frametime;
1568 decalfade = frametime * 255 / cl_decals_fadetime.value;
1569 decaltype = particletype + pt_decal;
1570 bloodtype = particletype + pt_blood;
1574 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1578 if (cl.free_particle > i)
1579 cl.free_particle = i;
1584 // heavily optimized decal case
1585 if (p->type == decaltype)
1587 // FIXME: this has fairly wacky handling of alpha
1588 if (cl.time > p->time2 + cl_decals_time.value)
1590 p->alpha -= decalfade;
1594 if (cl.free_particle > i)
1595 cl.free_particle = i;
1601 if (cl.entities[p->owner].render.model == p->ownermodel)
1603 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1604 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1609 if (cl.free_particle > i)
1610 cl.free_particle = i;
1616 if (p->delayedspawn)
1618 if (p->delayedspawn > cl.time)
1620 p->delayedspawn = 0;
1625 p->size += p->sizeincrease * frametime;
1626 p->alpha -= p->alphafade * frametime;
1628 if (p->alpha <= 0 || p->die <= cl.time)
1631 if (cl.free_particle > i)
1632 cl.free_particle = i;
1636 if (p->type->orientation != PARTICLE_BEAM && frametime > 0)
1638 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
1640 if (p->type == bloodtype)
1641 p->size += frametime * 8;
1643 p->vel[2] -= p->gravity * gravity;
1644 f = 1.0f - min(p->liquidfriction * frametime, 1);
1645 VectorScale(p->vel, f, p->vel);
1649 p->vel[2] -= p->gravity * gravity;
1652 f = 1.0f - min(p->airfriction * frametime, 1);
1653 VectorScale(p->vel, f, p->vel);
1657 VectorCopy(p->org, oldorg);
1658 VectorMA(p->org, frametime, p->vel, p->org);
1659 if (p->bounce && cl.time >= p->delayedcollisions)
1661 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);
1662 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1663 // or if the trace hit something flagged as NOIMPACT
1664 // then remove the particle
1665 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
1670 VectorCopy(trace.endpos, p->org);
1671 // react if the particle hit something
1672 if (trace.fraction < 1)
1674 VectorCopy(trace.endpos, p->org);
1675 if (p->type == particletype + pt_rain)
1677 // raindrop - splash on solid/water/slime/lava
1679 // convert from a raindrop particle to a rainsplash decal
1680 VectorCopy(trace.plane.normal, p->vel);
1681 VectorAdd(p->org, p->vel, p->org);
1682 p->type = particletype + pt_raindecal;
1683 p->texnum = tex_rainsplash;
1685 p->alphafade = p->alpha / 0.4;
1688 p->liquidfriction = 0;
1691 p->sizeincrease = p->size * 20;
1692 count = (int)lhrandom(1, 10);
1694 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);
1697 else if (p->type == bloodtype)
1699 // blood - splash on solid
1700 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1705 if (cl_stainmaps.integer)
1706 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)));
1707 if (!cl_decals.integer)
1712 // convert from a blood particle to a blood decal
1713 VectorCopy(trace.plane.normal, p->vel);
1714 VectorAdd(p->org, p->vel, p->org);
1716 p->type = particletype + pt_decal;
1717 p->texnum = tex_blooddecal[rand()&7];
1719 p->ownermodel = cl.entities[hitent].render.model;
1720 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1721 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1722 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1727 p->liquidfriction = 0;
1732 else if (p->bounce < 0)
1734 // bounce -1 means remove on impact
1740 // anything else - bounce off solid
1741 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1742 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1743 if (DotProduct(p->vel, p->vel) < 0.03)
1744 VectorClear(p->vel);
1750 if (p->type != particletype + pt_static)
1752 switch (p->type - particletype)
1754 case pt_entityparticle:
1755 // particle that removes itself after one rendered frame
1762 a = CL_PointSuperContents(p->org);
1763 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1767 a = CL_PointSuperContents(p->org);
1768 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1772 a = CL_PointSuperContents(p->org);
1773 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1777 if (cl.time > p->time2)
1780 p->time2 = cl.time + (rand() & 3) * 0.1;
1781 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1782 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1783 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1785 a = CL_PointSuperContents(p->org);
1786 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1794 cl.num_particles = maxparticle + 1;
1797 #define MAX_PARTICLETEXTURES 64
1798 // particletexture_t is a rectangle in the particlefonttexture
1799 typedef struct particletexture_s
1801 rtexture_t *texture;
1802 float s1, t1, s2, t2;
1806 static rtexturepool_t *particletexturepool;
1807 static rtexture_t *particlefonttexture;
1808 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1810 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1812 #define PARTICLETEXTURESIZE 64
1813 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1815 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1819 dz = 1 - (dx*dx+dy*dy);
1820 if (dz > 0) // it does hit the sphere
1824 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1825 VectorNormalize(normal);
1826 dot = DotProduct(normal, light);
1827 if (dot > 0.5) // interior reflection
1828 f += ((dot * 2) - 1);
1829 else if (dot < -0.5) // exterior reflection
1830 f += ((dot * -2) - 1);
1832 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1833 VectorNormalize(normal);
1834 dot = DotProduct(normal, light);
1835 if (dot > 0.5) // interior reflection
1836 f += ((dot * 2) - 1);
1837 else if (dot < -0.5) // exterior reflection
1838 f += ((dot * -2) - 1);
1840 f += 16; // just to give it a haze so you can see the outline
1841 f = bound(0, f, 255);
1842 return (unsigned char) f;
1848 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1850 int basex, basey, y;
1851 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1852 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1853 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1854 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1857 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1860 float cx, cy, dx, dy, f, iradius;
1862 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1863 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1864 iradius = 1.0f / radius;
1865 alpha *= (1.0f / 255.0f);
1866 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1868 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1872 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1875 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1876 d[0] += (int)(f * (red - d[0]));
1877 d[1] += (int)(f * (green - d[1]));
1878 d[2] += (int)(f * (blue - d[2]));
1884 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1887 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1889 data[0] = bound(minr, data[0], maxr);
1890 data[1] = bound(ming, data[1], maxg);
1891 data[2] = bound(minb, data[2], maxb);
1895 void particletextureinvert(unsigned char *data)
1898 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1900 data[0] = 255 - data[0];
1901 data[1] = 255 - data[1];
1902 data[2] = 255 - data[2];
1906 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1907 static void R_InitBloodTextures (unsigned char *particletexturedata)
1910 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1913 for (i = 0;i < 8;i++)
1915 memset(&data[0][0][0], 255, sizeof(data));
1916 for (k = 0;k < 24;k++)
1917 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1918 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1919 particletextureinvert(&data[0][0][0]);
1920 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1924 for (i = 0;i < 8;i++)
1926 memset(&data[0][0][0], 255, sizeof(data));
1928 for (j = 1;j < 10;j++)
1929 for (k = min(j, m - 1);k < m;k++)
1930 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1931 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1932 particletextureinvert(&data[0][0][0]);
1933 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1938 //uncomment this to make engine save out particle font to a tga file when run
1939 //#define DUMPPARTICLEFONT
1941 static void R_InitParticleTexture (void)
1943 int x, y, d, i, k, m;
1947 // a note: decals need to modulate (multiply) the background color to
1948 // properly darken it (stain), and they need to be able to alpha fade,
1949 // this is a very difficult challenge because it means fading to white
1950 // (no change to background) rather than black (darkening everything
1951 // behind the whole decal polygon), and to accomplish this the texture is
1952 // inverted (dark red blood on white background becomes brilliant cyan
1953 // and white on black background) so we can alpha fade it to black, then
1954 // we invert it again during the blendfunc to make it work...
1956 #ifndef DUMPPARTICLEFONT
1957 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE, true);
1958 if (!particlefonttexture)
1961 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1962 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1963 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1966 for (i = 0;i < 8;i++)
1968 memset(&data[0][0][0], 255, sizeof(data));
1971 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1973 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1974 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1976 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1978 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1979 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1981 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1982 d = (noise2[y][x] - 128) * 3 + 192;
1984 d = (int)(d * (1-(dx*dx+dy*dy)));
1985 d = (d * noise1[y][x]) >> 7;
1986 d = bound(0, d, 255);
1987 data[y][x][3] = (unsigned char) d;
1994 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1998 memset(&data[0][0][0], 255, sizeof(data));
1999 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2001 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2002 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2004 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2005 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2006 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
2009 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
2012 memset(&data[0][0][0], 255, sizeof(data));
2013 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2015 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2016 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2018 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2019 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2020 d = bound(0, d, 255);
2021 data[y][x][3] = (unsigned char) d;
2024 setuptex(tex_particle, &data[0][0][0], particletexturedata);
2027 memset(&data[0][0][0], 255, sizeof(data));
2028 light[0] = 1;light[1] = 1;light[2] = 1;
2029 VectorNormalize(light);
2030 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2032 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2033 // stretch upper half of bubble by +50% and shrink lower half by -50%
2034 // (this gives an elongated teardrop shape)
2036 dy = (dy - 0.5f) * 2.0f;
2038 dy = (dy - 0.5f) / 1.5f;
2039 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2041 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2042 // shrink bubble width to half
2044 data[y][x][3] = shadebubble(dx, dy, light);
2047 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2050 memset(&data[0][0][0], 255, sizeof(data));
2051 light[0] = 1;light[1] = 1;light[2] = 1;
2052 VectorNormalize(light);
2053 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2055 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2056 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2058 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2059 data[y][x][3] = shadebubble(dx, dy, light);
2062 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2064 // Blood particles and blood decals
2065 R_InitBloodTextures (particletexturedata);
2068 for (i = 0;i < 8;i++)
2070 memset(&data[0][0][0], 255, sizeof(data));
2071 for (k = 0;k < 12;k++)
2072 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2073 for (k = 0;k < 3;k++)
2074 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2075 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2076 particletextureinvert(&data[0][0][0]);
2077 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2080 #ifdef DUMPPARTICLEFONT
2081 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2084 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2086 Mem_Free(particletexturedata);
2088 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2090 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2091 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2092 particletexture[i].texture = particlefonttexture;
2093 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2094 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2095 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2096 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2099 #ifndef DUMPPARTICLEFONT
2100 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE, true);
2101 if (!particletexture[tex_beam].texture)
2104 unsigned char noise3[64][64], data2[64][16][4];
2106 fractalnoise(&noise3[0][0], 64, 4);
2108 for (y = 0;y < 64;y++)
2110 dy = (y - 0.5f*64) / (64*0.5f-1);
2111 for (x = 0;x < 16;x++)
2113 dx = (x - 0.5f*16) / (16*0.5f-2);
2114 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2115 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2116 data2[y][x][3] = 255;
2120 #ifdef DUMPPARTICLEFONT
2121 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2123 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2125 particletexture[tex_beam].s1 = 0;
2126 particletexture[tex_beam].t1 = 0;
2127 particletexture[tex_beam].s2 = 1;
2128 particletexture[tex_beam].t2 = 1;
2131 static void r_part_start(void)
2133 particletexturepool = R_AllocTexturePool();
2134 R_InitParticleTexture ();
2135 CL_Particles_LoadEffectInfo();
2138 static void r_part_shutdown(void)
2140 R_FreeTexturePool(&particletexturepool);
2143 static void r_part_newmap(void)
2147 #define BATCHSIZE 256
2148 int particle_element3i[BATCHSIZE*6];
2149 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2151 void R_Particles_Init (void)
2154 for (i = 0;i < BATCHSIZE;i++)
2156 particle_element3i[i*6+0] = i*4+0;
2157 particle_element3i[i*6+1] = i*4+1;
2158 particle_element3i[i*6+2] = i*4+2;
2159 particle_element3i[i*6+3] = i*4+0;
2160 particle_element3i[i*6+4] = i*4+2;
2161 particle_element3i[i*6+5] = i*4+3;
2164 Cvar_RegisterVariable(&r_drawparticles);
2165 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2168 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2170 int surfacelistindex;
2171 int batchstart, batchcount;
2172 const particle_t *p;
2174 rtexture_t *texture;
2175 float *v3f, *t2f, *c4f;
2177 R_Mesh_Matrix(&identitymatrix);
2178 R_Mesh_ResetTextureState();
2179 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2180 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2181 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2182 GL_DepthMask(false);
2183 GL_DepthRange(0, 1);
2184 GL_PolygonOffset(0, 0);
2186 GL_CullFace(GL_NONE);
2188 // first generate all the vertices at once
2189 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2191 particletexture_t *tex;
2193 float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
2195 p = cl.particles + surfacelist[surfacelistindex];
2197 blendmode = p->type->blendmode;
2199 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2200 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2201 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2202 ca = p->alpha * (1.0f / 255.0f);
2203 if (blendmode == PBLEND_MOD)
2213 ca *= cl_particles_alpha.value;
2214 if (p->type->lighting)
2216 float ambient[3], diffuse[3], diffusenormal[3];
2217 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2218 cr *= (ambient[0] + 0.5 * diffuse[0]);
2219 cg *= (ambient[1] + 0.5 * diffuse[1]);
2220 cb *= (ambient[2] + 0.5 * diffuse[2]);
2222 if (r_refdef.fogenabled)
2224 fog = FogPoint_World(p->org);
2228 if (blendmode == PBLEND_ALPHA)
2231 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2232 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2233 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2236 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2237 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2238 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2239 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2241 size = p->size * cl_particles_size.value;
2243 tex = &particletexture[p->texnum];
2244 if (p->type->orientation == PARTICLE_BILLBOARD)
2246 VectorScale(r_view.left, -size, right);
2247 VectorScale(r_view.up, size, up);
2248 v3f[ 0] = org[0] - right[0] - up[0];
2249 v3f[ 1] = org[1] - right[1] - up[1];
2250 v3f[ 2] = org[2] - right[2] - up[2];
2251 v3f[ 3] = org[0] - right[0] + up[0];
2252 v3f[ 4] = org[1] - right[1] + up[1];
2253 v3f[ 5] = org[2] - right[2] + up[2];
2254 v3f[ 6] = org[0] + right[0] + up[0];
2255 v3f[ 7] = org[1] + right[1] + up[1];
2256 v3f[ 8] = org[2] + right[2] + up[2];
2257 v3f[ 9] = org[0] + right[0] - up[0];
2258 v3f[10] = org[1] + right[1] - up[1];
2259 v3f[11] = org[2] + right[2] - up[2];
2260 t2f[0] = tex->s1;t2f[1] = tex->t2;
2261 t2f[2] = tex->s1;t2f[3] = tex->t1;
2262 t2f[4] = tex->s2;t2f[5] = tex->t1;
2263 t2f[6] = tex->s2;t2f[7] = tex->t2;
2265 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2267 VectorVectors(p->vel, right, up);
2268 VectorScale(right, size, right);
2269 VectorScale(up, size, up);
2270 v3f[ 0] = org[0] - right[0] - up[0];
2271 v3f[ 1] = org[1] - right[1] - up[1];
2272 v3f[ 2] = org[2] - right[2] - up[2];
2273 v3f[ 3] = org[0] - right[0] + up[0];
2274 v3f[ 4] = org[1] - right[1] + up[1];
2275 v3f[ 5] = org[2] - right[2] + up[2];
2276 v3f[ 6] = org[0] + right[0] + up[0];
2277 v3f[ 7] = org[1] + right[1] + up[1];
2278 v3f[ 8] = org[2] + right[2] + up[2];
2279 v3f[ 9] = org[0] + right[0] - up[0];
2280 v3f[10] = org[1] + right[1] - up[1];
2281 v3f[11] = org[2] + right[2] - up[2];
2282 t2f[0] = tex->s1;t2f[1] = tex->t2;
2283 t2f[2] = tex->s1;t2f[3] = tex->t1;
2284 t2f[4] = tex->s2;t2f[5] = tex->t1;
2285 t2f[6] = tex->s2;t2f[7] = tex->t2;
2287 else if (p->type->orientation == PARTICLE_SPARK)
2289 VectorMA(org, -0.02, p->vel, v);
2290 VectorMA(org, 0.02, p->vel, up2);
2291 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2292 t2f[0] = tex->s1;t2f[1] = tex->t2;
2293 t2f[2] = tex->s1;t2f[3] = tex->t1;
2294 t2f[4] = tex->s2;t2f[5] = tex->t1;
2295 t2f[6] = tex->s2;t2f[7] = tex->t2;
2297 else if (p->type->orientation == PARTICLE_BEAM)
2299 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2300 VectorSubtract(p->vel, org, up);
2301 VectorNormalize(up);
2302 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2303 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2304 t2f[0] = 1;t2f[1] = v[0];
2305 t2f[2] = 0;t2f[3] = v[0];
2306 t2f[4] = 0;t2f[5] = v[1];
2307 t2f[6] = 1;t2f[7] = v[1];
2311 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2316 // now render batches of particles based on blendmode and texture
2317 blendmode = PBLEND_ADD;
2318 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2319 texture = particletexture[63].texture;
2320 R_Mesh_TexBind(0, R_GetTexture(texture));
2321 GL_LockArrays(0, numsurfaces*4);
2324 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2326 p = cl.particles + surfacelist[surfacelistindex];
2328 if (blendmode != p->type->blendmode)
2331 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2333 batchstart = surfacelistindex;
2334 blendmode = p->type->blendmode;
2335 if (blendmode == PBLEND_ALPHA)
2336 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2337 else if (blendmode == PBLEND_ADD)
2338 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2339 else //if (blendmode == PBLEND_MOD)
2340 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2342 if (texture != particletexture[p->texnum].texture)
2345 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2347 batchstart = surfacelistindex;
2348 texture = particletexture[p->texnum].texture;
2349 R_Mesh_TexBind(0, R_GetTexture(texture));
2355 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2356 GL_LockArrays(0, 0);
2359 void R_DrawParticles (void)
2362 float minparticledist;
2365 // LordHavoc: early out conditions
2366 if ((!cl.num_particles) || (!r_drawparticles.integer))
2369 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2371 // LordHavoc: only render if not too close
2372 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2374 if (p->type && !p->delayedspawn)
2376 r_refdef.stats.particles++;
2377 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2378 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);