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 and reduces their alpha"};
173 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
174 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
175 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
176 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "0.5", "opacity of blood"};
177 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
178 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
179 cvar_t cl_particles_explosions_smoke = {CVAR_SAVE, "cl_particles_explosions_smokes", "0", "enables smoke from explosions"};
180 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
181 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
182 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
183 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
184 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
185 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
186 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
187 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
188 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
189 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
190 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
191 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
194 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
200 particleeffectinfo_t *info = NULL;
201 const char *text = textstart;
203 effectinfoindex = -1;
204 for (linenumber = 1;;linenumber++)
207 for (arrayindex = 0;arrayindex < 16;arrayindex++)
208 argv[arrayindex][0] = 0;
211 if (!COM_ParseToken(&text, true))
213 if (!strcmp(com_token, "\n"))
217 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
223 #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;}
224 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
225 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
226 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
227 #define readfloat(var) checkparms(2);var = atof(argv[1])
228 if (!strcmp(argv[0], "effect"))
233 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
235 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
238 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
240 if (particleeffectname[effectnameindex][0])
242 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
247 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
251 // if we run out of names, abort
252 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
254 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
257 info = particleeffectinfo + effectinfoindex;
258 info->effectnameindex = effectnameindex;
259 info->particletype = pt_alphastatic;
260 info->tex[0] = tex_particle;
261 info->tex[1] = tex_particle;
262 info->color[0] = 0xFFFFFF;
263 info->color[1] = 0xFFFFFF;
267 info->alpha[1] = 256;
268 info->alpha[2] = 256;
269 info->time[0] = 9999;
270 info->time[1] = 9999;
271 VectorSet(info->lightcolor, 1, 1, 1);
272 info->lightshadow = true;
273 info->lighttime = 9999;
275 else if (info == NULL)
277 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
280 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
281 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
282 else if (!strcmp(argv[0], "type"))
285 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
286 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
287 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
288 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
289 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
290 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
291 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
292 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
293 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
294 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
295 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
296 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
297 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
299 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
300 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
301 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
302 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
303 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
304 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
305 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
306 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
307 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
308 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
309 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
310 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
311 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
312 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
313 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
314 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
315 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
316 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
317 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
318 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
319 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
320 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
321 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
322 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
324 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
333 int CL_ParticleEffectIndexForName(const char *name)
336 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
337 if (!strcmp(particleeffectname[i], name))
342 const char *CL_ParticleEffectNameForIndex(int i)
344 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
346 return particleeffectname[i];
349 // MUST match effectnameindex_t in client.h
350 static const char *standardeffectnames[EFFECT_TOTAL] =
374 "TE_TEI_BIGEXPLOSION",
390 void CL_Particles_LoadEffectInfo(void)
393 unsigned char *filedata;
394 fs_offset_t filesize;
395 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
396 memset(particleeffectname, 0, sizeof(particleeffectname));
397 for (i = 0;i < EFFECT_TOTAL;i++)
398 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
399 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
402 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
412 void CL_ReadPointFile_f (void);
413 void CL_Particles_Init (void)
415 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)");
416 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
418 Cvar_RegisterVariable (&cl_particles);
419 Cvar_RegisterVariable (&cl_particles_quality);
420 Cvar_RegisterVariable (&cl_particles_size);
421 Cvar_RegisterVariable (&cl_particles_quake);
422 Cvar_RegisterVariable (&cl_particles_blood);
423 Cvar_RegisterVariable (&cl_particles_blood_alpha);
424 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
425 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
426 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
427 Cvar_RegisterVariable (&cl_particles_explosions_shell);
428 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
429 Cvar_RegisterVariable (&cl_particles_rain);
430 Cvar_RegisterVariable (&cl_particles_snow);
431 Cvar_RegisterVariable (&cl_particles_smoke);
432 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
433 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
434 Cvar_RegisterVariable (&cl_particles_sparks);
435 Cvar_RegisterVariable (&cl_particles_bubbles);
436 Cvar_RegisterVariable (&cl_decals);
437 Cvar_RegisterVariable (&cl_decals_time);
438 Cvar_RegisterVariable (&cl_decals_fadetime);
441 void CL_Particles_Shutdown (void)
445 // list of all 26 parameters:
446 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
447 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
448 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
449 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
450 // palpha - opacity of particle as 0-255 (can be more than 255)
451 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
452 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
453 // pgravity - how much effect gravity has on the particle (0-1)
454 // 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
455 // px,py,pz - starting origin of particle
456 // pvx,pvy,pvz - starting velocity of particle
457 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
458 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)
463 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
464 if (cl.free_particle >= cl.max_particles)
466 part = &cl.particles[cl.free_particle++];
467 if (cl.num_particles < cl.free_particle)
468 cl.num_particles = cl.free_particle;
469 memset(part, 0, sizeof(*part));
471 l2 = (int)lhrandom(0.5, 256.5);
473 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
474 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
475 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
476 part->color[3] = 0xFF;
479 part->sizeincrease = psizeincrease;
480 part->alpha = palpha;
481 part->alphafade = palphafade;
482 part->gravity = pgravity;
483 part->bounce = pbounce;
485 part->org[0] = px + originjitter * v[0];
486 part->org[1] = py + originjitter * v[1];
487 part->org[2] = pz + originjitter * v[2];
488 part->vel[0] = pvx + velocityjitter * v[0];
489 part->vel[1] = pvy + velocityjitter * v[1];
490 part->vel[2] = pvz + velocityjitter * v[2];
492 part->airfriction = pairfriction;
493 part->liquidfriction = pliquidfriction;
494 part->delayedcollisions = 0;
498 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
501 if (!cl_decals.integer)
503 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);
508 p->ownermodel = cl.entities[p->owner].render.model;
509 VectorAdd(org, normal, p->org);
510 VectorCopy(normal, p->vel);
511 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
512 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
513 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
517 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
520 float bestfrac, bestorg[3], bestnormal[3];
522 int besthitent = 0, hitent;
525 for (i = 0;i < 32;i++)
528 VectorMA(org, maxdist, org2, org2);
529 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
530 // take the closest trace result that doesn't end up hitting a NOMARKS
531 // surface (sky for example)
532 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
534 bestfrac = trace.fraction;
536 VectorCopy(trace.endpos, bestorg);
537 VectorCopy(trace.plane.normal, bestnormal);
541 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
544 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
545 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
546 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)
549 matrix4x4_t tempmatrix;
550 VectorLerp(originmins, 0.5, originmaxs, center);
551 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
552 if (effectnameindex == EFFECT_SVC_PARTICLE)
554 if (cl_particles.integer)
556 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
558 CL_ParticleExplosion(center);
559 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
560 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
563 count *= cl_particles_quality.value;
564 for (;count > 0;count--)
566 int k = particlepalette[palettecolor + (rand()&7)];
567 if (cl_particles_quake.integer)
568 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);
569 else if (gamemode == GAME_GOODVSBAD2)
570 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);
572 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);
577 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
578 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
579 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
580 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
581 else if (effectnameindex == EFFECT_TE_SPIKE)
583 if (cl_particles_bulletimpacts.integer)
585 if (cl_particles_quake.integer)
587 if (cl_particles_smoke.integer)
588 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
592 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
593 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
597 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
598 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
600 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
602 if (cl_particles_bulletimpacts.integer)
604 if (cl_particles_quake.integer)
606 if (cl_particles_smoke.integer)
607 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
611 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
612 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
616 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
617 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
618 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);
620 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
622 if (cl_particles_bulletimpacts.integer)
624 if (cl_particles_quake.integer)
626 if (cl_particles_smoke.integer)
627 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
631 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
632 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
636 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
637 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
639 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
641 if (cl_particles_bulletimpacts.integer)
643 if (cl_particles_quake.integer)
645 if (cl_particles_smoke.integer)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
650 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
651 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
655 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
656 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
657 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);
659 else if (effectnameindex == EFFECT_TE_BLOOD)
661 if (!cl_particles_blood.integer)
663 if (cl_particles_quake.integer)
664 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
667 static double bloodaccumulator = 0;
668 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
669 for (;bloodaccumulator > 0;bloodaccumulator--)
670 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);
673 else if (effectnameindex == EFFECT_TE_SPARK)
674 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
675 else if (effectnameindex == EFFECT_TE_PLASMABURN)
677 // plasma scorch mark
678 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
679 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
680 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
682 else if (effectnameindex == EFFECT_TE_GUNSHOT)
684 if (cl_particles_bulletimpacts.integer)
686 if (cl_particles_quake.integer)
687 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
690 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
691 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
695 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
696 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
698 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
700 if (cl_particles_bulletimpacts.integer)
702 if (cl_particles_quake.integer)
703 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
706 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
707 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
711 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
712 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
713 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);
715 else if (effectnameindex == EFFECT_TE_EXPLOSION)
717 CL_ParticleExplosion(center);
718 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);
720 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
722 CL_ParticleExplosion(center);
723 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);
725 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
727 if (cl_particles_quake.integer)
730 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
733 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);
735 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);
739 CL_ParticleExplosion(center);
740 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);
742 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
743 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);
744 else if (effectnameindex == EFFECT_TE_FLAMEJET)
746 count *= cl_particles_quality.value;
748 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);
750 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
752 float i, j, inc, vel;
755 inc = 8 / cl_particles_quality.value;
756 for (i = -128;i < 128;i += inc)
758 for (j = -128;j < 128;j += inc)
760 dir[0] = j + lhrandom(0, inc);
761 dir[1] = i + lhrandom(0, inc);
763 org[0] = center[0] + dir[0];
764 org[1] = center[1] + dir[1];
765 org[2] = center[2] + lhrandom(0, 64);
766 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
767 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);
771 else if (effectnameindex == EFFECT_TE_TELEPORT)
773 float i, j, k, inc, vel;
776 inc = 8 / cl_particles_quality.value;
777 for (i = -16;i < 16;i += inc)
779 for (j = -16;j < 16;j += inc)
781 for (k = -24;k < 32;k += inc)
783 VectorSet(dir, i*8, j*8, k*8);
784 VectorNormalize(dir);
785 vel = lhrandom(50, 113);
786 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);
790 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);
791 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);
793 else if (effectnameindex == EFFECT_TE_TEI_G3)
794 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);
795 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
797 if (cl_particles_smoke.integer)
799 count *= 0.25f * cl_particles_quality.value;
801 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);
804 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
806 CL_ParticleExplosion(center);
807 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);
809 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
812 if (cl_stainmaps.integer)
813 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
814 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
815 if (cl_particles_smoke.integer)
816 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
817 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);
818 if (cl_particles_sparks.integer)
819 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
820 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);
821 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);
823 else if (effectnameindex == EFFECT_EF_FLAME)
825 count *= 300 * cl_particles_quality.value;
827 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);
828 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);
830 else if (effectnameindex == EFFECT_EF_STARDUST)
832 count *= 200 * cl_particles_quality.value;
834 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);
835 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);
837 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
841 int smoke, blood, bubbles, r, color;
843 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
846 Vector4Set(light, 0, 0, 0, 0);
848 if (effectnameindex == EFFECT_TR_ROCKET)
849 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
850 else if (effectnameindex == EFFECT_TR_VORESPIKE)
852 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
853 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
855 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
857 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
858 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
862 matrix4x4_t tempmatrix;
863 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
864 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);
871 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
874 VectorSubtract(originmaxs, originmins, dir);
875 len = VectorNormalizeLength(dir);
878 dec = -ent->persistent.trail_time;
879 ent->persistent.trail_time += len;
880 if (ent->persistent.trail_time < 0.01f)
883 // if we skip out, leave it reset
884 ent->persistent.trail_time = 0.0f;
889 // advance into this frame to reach the first puff location
890 VectorMA(originmins, dec, dir, pos);
893 smoke = cl_particles.integer && cl_particles_smoke.integer;
894 blood = cl_particles.integer && cl_particles_blood.integer;
895 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
896 qd = 1.0f / cl_particles_quality.value;
903 if (effectnameindex == EFFECT_TR_BLOOD)
905 if (cl_particles_quake.integer)
907 color = particlepalette[67 + (rand()&3)];
908 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);
913 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);
916 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
918 if (cl_particles_quake.integer)
921 color = particlepalette[67 + (rand()&3)];
922 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);
927 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);
933 if (effectnameindex == EFFECT_TR_ROCKET)
935 if (cl_particles_quake.integer)
938 color = particlepalette[ramp3[r]];
939 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);
943 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);
944 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);
947 else if (effectnameindex == EFFECT_TR_GRENADE)
949 if (cl_particles_quake.integer)
952 color = particlepalette[ramp3[r]];
953 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);
957 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);
960 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
962 if (cl_particles_quake.integer)
965 color = particlepalette[52 + (rand()&7)];
966 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);
967 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);
969 else if (gamemode == GAME_GOODVSBAD2)
972 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);
976 color = particlepalette[20 + (rand()&7)];
977 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);
980 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
982 if (cl_particles_quake.integer)
985 color = particlepalette[230 + (rand()&7)];
986 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);
987 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);
991 color = particlepalette[226 + (rand()&7)];
992 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);
995 else if (effectnameindex == EFFECT_TR_VORESPIKE)
997 if (cl_particles_quake.integer)
999 color = particlepalette[152 + (rand()&3)];
1000 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);
1002 else if (gamemode == GAME_GOODVSBAD2)
1005 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);
1007 else if (gamemode == GAME_PRYDON)
1010 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);
1013 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);
1015 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1018 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);
1020 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1023 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);
1025 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1026 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);
1030 if (effectnameindex == EFFECT_TR_ROCKET)
1031 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);
1032 else if (effectnameindex == EFFECT_TR_GRENADE)
1033 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);
1035 // advance to next time and position
1038 VectorMA (pos, dec, dir, pos);
1041 ent->persistent.trail_time = len;
1043 else if (developer.integer >= 1)
1044 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1047 // this is also called on point effects with spawndlight = true and
1048 // spawnparticles = true
1049 // it is called CL_ParticleTrail because most code does not want to supply
1050 // these parameters, only trail handling does
1051 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)
1054 qboolean found = false;
1055 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1056 return; // invalid effect index
1057 if (!particleeffectname[effectnameindex][0])
1058 return; // no such effect
1059 VectorLerp(originmins, 0.5, originmaxs, center);
1060 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1062 int effectinfoindex;
1065 particleeffectinfo_t *info;
1068 vec3_t centervelocity;
1074 qboolean underwater;
1076 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1077 VectorLerp(originmins, 0.5, originmaxs, center);
1078 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1079 supercontents = CL_PointSuperContents(center);
1080 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1081 VectorSubtract(originmaxs, originmins, traildir);
1082 traillen = VectorLength(traildir);
1083 VectorNormalize(traildir);
1084 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1086 if (info->effectnameindex == effectnameindex)
1089 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1091 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1094 // spawn a dlight if requested
1095 if (info->lightradiusstart > 0 && spawndlight)
1097 matrix4x4_t tempmatrix;
1098 if (info->trailspacing > 0)
1099 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1101 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1102 if (info->lighttime > 0 && info->lightradiusfade > 0)
1104 // light flash (explosion, etc)
1105 // called when effect starts
1106 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);
1111 // called by CL_LinkNetworkEntity
1112 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1113 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);
1117 if (!spawnparticles)
1122 if (info->tex[1] > info->tex[0])
1124 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1125 tex = min(tex, info->tex[1] - 1);
1127 if (info->particletype == pt_decal)
1128 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]);
1129 else if (info->particletype == pt_beam)
1130 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);
1133 if (!cl_particles.integer)
1135 switch (info->particletype)
1137 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1138 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1139 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1140 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1141 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1142 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1145 VectorCopy(originmins, trailpos);
1146 if (info->trailspacing > 0)
1148 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1149 trailstep = info->trailspacing / cl_particles_quality.value;
1153 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1156 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1157 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1159 if (info->tex[1] > info->tex[0])
1161 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1162 tex = min(tex, info->tex[1] - 1);
1166 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1167 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1168 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1171 p = 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);
1172 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
1173 if (info->bounce != 0 && info->gravity == 0)
1175 float lifetime = p->alpha / (p->alphafade ? p->alphafade : 1);
1176 VectorMA(p->org, lifetime, p->vel, rvec);
1177 trace = CL_Move(p->org, vec3_origin, vec3_origin, rvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((info->particletype == pt_rain || info->particletype == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, NULL, false);
1178 p->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
1181 VectorMA(trailpos, trailstep, traildir, trailpos);
1188 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1191 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)
1193 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1201 void CL_EntityParticles (const entity_t *ent)
1204 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1205 static vec3_t avelocities[NUMVERTEXNORMALS];
1206 if (!cl_particles.integer) return;
1208 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1210 if (!avelocities[0][0])
1211 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1212 avelocities[0][i] = lhrandom(0, 2.55);
1214 for (i = 0;i < NUMVERTEXNORMALS;i++)
1216 yaw = cl.time * avelocities[i][0];
1217 pitch = cl.time * avelocities[i][1];
1218 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1219 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1220 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1221 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);
1226 void CL_ReadPointFile_f (void)
1228 vec3_t org, leakorg;
1230 char *pointfile = NULL, *pointfilepos, *t, tchar;
1231 char name[MAX_OSPATH];
1236 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1237 strlcat (name, ".pts", sizeof (name));
1238 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1241 Con_Printf("Could not open %s\n", name);
1245 Con_Printf("Reading %s...\n", name);
1246 VectorClear(leakorg);
1249 pointfilepos = pointfile;
1250 while (*pointfilepos)
1252 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1257 while (*t && *t != '\n' && *t != '\r')
1261 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1267 VectorCopy(org, leakorg);
1270 if (cl.num_particles < cl.max_particles - 3)
1273 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);
1276 Mem_Free(pointfile);
1277 VectorCopy(leakorg, org);
1278 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1280 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);
1281 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);
1282 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);
1287 CL_ParseParticleEffect
1289 Parse an effect out of the server message
1292 void CL_ParseParticleEffect (void)
1295 int i, count, msgcount, color;
1297 MSG_ReadVector(org, cls.protocol);
1298 for (i=0 ; i<3 ; i++)
1299 dir[i] = MSG_ReadChar ();
1300 msgcount = MSG_ReadByte ();
1301 color = MSG_ReadByte ();
1303 if (msgcount == 255)
1308 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1313 CL_ParticleExplosion
1317 void CL_ParticleExplosion (const vec3_t org)
1323 if (cl_stainmaps.integer)
1324 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1325 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1327 if (cl_particles_quake.integer)
1329 for (i = 0;i < 1024;i++)
1335 color = particlepalette[ramp1[r]];
1336 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);
1340 color = particlepalette[ramp2[r]];
1341 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);
1347 i = CL_PointSuperContents(org);
1348 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1350 if (cl_particles.integer && cl_particles_bubbles.integer)
1351 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1352 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);
1356 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1358 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1360 for (i = 0;i < 32;i++)
1364 for (k = 0;k < 16;k++)
1366 v[0] = org[0] + lhrandom(-48, 48);
1367 v[1] = org[1] + lhrandom(-48, 48);
1368 v[2] = org[2] + lhrandom(-48, 48);
1369 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1370 if (trace.fraction >= 0.1)
1373 VectorSubtract(trace.endpos, org, v2);
1374 VectorScale(v2, 2.0f, v2);
1375 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);
1379 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1381 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1385 for (k = 0;k < 16;k++)
1388 VectorMA(org, 128, v2, v);
1389 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1390 if (trace.fraction >= 0.1)
1393 VectorSubtract(trace.endpos, org, v2);
1394 VectorScale(v2, 2.0f, v2);
1395 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);
1401 if (cl_particles_explosions_shell.integer)
1402 R_NewExplosion(org);
1407 CL_ParticleExplosion2
1411 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1414 if (!cl_particles.integer) return;
1416 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1418 k = particlepalette[colorStart + (i % colorLength)];
1419 if (cl_particles_quake.integer)
1420 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);
1422 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);
1426 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1428 if (cl_particles_sparks.integer)
1430 sparkcount *= cl_particles_quality.value;
1431 while(sparkcount-- > 0)
1432 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]) + sv_gravity.value * 0.1f, 0, 0, 0, 64);
1436 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1438 if (cl_particles_smoke.integer)
1440 smokecount *= cl_particles_quality.value;
1441 while(smokecount-- > 0)
1442 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);
1446 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)
1449 if (!cl_particles.integer) return;
1451 count = (int)(count * cl_particles_quality.value);
1454 k = particlepalette[colorbase + (rand()&3)];
1455 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);
1459 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1462 float z, minz, maxz;
1464 if (!cl_particles.integer) return;
1465 if (dir[2] < 0) // falling
1470 minz = z - fabs(dir[2]) * 0.1;
1471 maxz = z + fabs(dir[2]) * 0.1;
1472 minz = bound(mins[2], minz, maxs[2]);
1473 maxz = bound(mins[2], maxz, maxs[2]);
1475 count = (int)(count * cl_particles_quality.value);
1480 if (!cl_particles_rain.integer) break;
1481 count *= 4; // ick, this should be in the mod or maps?
1485 k = particlepalette[colorbase + (rand()&3)];
1486 if (gamemode == GAME_GOODVSBAD2)
1487 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);
1489 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);
1493 if (!cl_particles_snow.integer) break;
1496 k = particlepalette[colorbase + (rand()&3)];
1497 if (gamemode == GAME_GOODVSBAD2)
1498 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);
1500 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);
1502 VectorCopy(p->vel, p->relativedirection);
1506 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1515 void CL_MoveParticles (void)
1518 int i, maxparticle, j, a, content;
1519 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1520 particletype_t *decaltype, *bloodtype;
1524 // LordHavoc: early out condition
1525 if (!cl.num_particles)
1527 cl.free_particle = 0;
1531 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1532 gravity = frametime * sv_gravity.value;
1533 dvel = 1+4*frametime;
1534 decalfade = frametime * 255 / cl_decals_fadetime.value;
1535 decaltype = particletype + pt_decal;
1536 bloodtype = particletype + pt_blood;
1540 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1544 if (cl.free_particle > i)
1545 cl.free_particle = i;
1550 // heavily optimized decal case
1551 if (p->type == decaltype)
1553 // FIXME: this has fairly wacky handling of alpha
1554 if (cl.time > p->time2 + cl_decals_time.value)
1556 p->alpha -= decalfade;
1560 if (cl.free_particle > i)
1561 cl.free_particle = i;
1567 if (cl.entities[p->owner].render.model == p->ownermodel)
1569 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1570 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1575 if (cl.free_particle > i)
1576 cl.free_particle = i;
1584 p->size += p->sizeincrease * frametime;
1585 p->alpha -= p->alphafade * frametime;
1590 if (cl.free_particle > i)
1591 cl.free_particle = i;
1595 if (p->type->orientation != PARTICLE_BEAM)
1597 VectorCopy(p->org, oldorg);
1598 VectorMA(p->org, frametime, p->vel, p->org);
1599 VectorCopy(p->org, org);
1600 if (p->bounce && cl.time >= p->delayedcollisions)
1602 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);
1603 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1604 // or if the trace hit something flagged as NOIMPACT
1605 // then remove the particle
1606 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1611 // react if the particle hit something
1612 if (trace.fraction < 1)
1614 VectorCopy(trace.endpos, p->org);
1615 if (p->type == particletype + pt_rain)
1617 // raindrop - splash on solid/water/slime/lava
1619 // convert from a raindrop particle to a rainsplash decal
1620 VectorCopy(trace.plane.normal, p->vel);
1621 VectorAdd(p->org, p->vel, p->org);
1622 p->type = particletype + pt_raindecal;
1623 p->texnum = tex_rainsplash;
1625 p->alphafade = p->alpha / 0.4;
1628 p->liquidfriction = 0;
1631 p->sizeincrease = p->size * 2;
1634 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, sv_gravity.value * 0.04 + p->vel[2]*16, 0, 0, 0, 32);
1636 else if (p->type == bloodtype)
1638 // blood - splash on solid
1639 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1644 if (cl_stainmaps.integer)
1645 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)));
1646 if (!cl_decals.integer)
1651 // convert from a blood particle to a blood decal
1652 VectorCopy(trace.plane.normal, p->vel);
1653 VectorAdd(p->org, p->vel, p->org);
1655 p->type = particletype + pt_decal;
1656 p->texnum = tex_blooddecal[rand()&7];
1658 p->ownermodel = cl.entities[hitent].render.model;
1659 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1660 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1661 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1666 p->liquidfriction = 0;
1670 else if (p->bounce < 0)
1672 // bounce -1 means remove on impact
1678 // anything else - bounce off solid
1679 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1680 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1681 if (DotProduct(p->vel, p->vel) < 0.03)
1682 VectorClear(p->vel);
1686 p->vel[2] -= p->gravity * gravity;
1688 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1690 f = 1.0f - min(p->liquidfriction * frametime, 1);
1691 VectorScale(p->vel, f, p->vel);
1693 else if (p->airfriction)
1695 f = 1.0f - min(p->airfriction * frametime, 1);
1696 VectorScale(p->vel, f, p->vel);
1700 if (p->type != particletype + pt_static)
1702 switch (p->type - particletype)
1704 case pt_entityparticle:
1705 // particle that removes itself after one rendered frame
1712 a = CL_PointSuperContents(p->org);
1713 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1715 p->size += frametime * 8;
1716 //p->alpha -= bloodwaterfade;
1719 p->vel[2] -= gravity;
1720 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1724 a = CL_PointSuperContents(p->org);
1725 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1732 a = CL_PointSuperContents(p->org);
1733 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1737 if (cl.time > p->time2)
1740 p->time2 = cl.time + (rand() & 3) * 0.1;
1741 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1742 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1743 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1745 a = CL_PointSuperContents(p->org);
1746 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1754 cl.num_particles = maxparticle + 1;
1757 #define MAX_PARTICLETEXTURES 64
1758 // particletexture_t is a rectangle in the particlefonttexture
1759 typedef struct particletexture_s
1761 rtexture_t *texture;
1762 float s1, t1, s2, t2;
1766 static rtexturepool_t *particletexturepool;
1767 static rtexture_t *particlefonttexture;
1768 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1770 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1772 #define PARTICLETEXTURESIZE 64
1773 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1775 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1779 dz = 1 - (dx*dx+dy*dy);
1780 if (dz > 0) // it does hit the sphere
1784 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1785 VectorNormalize(normal);
1786 dot = DotProduct(normal, light);
1787 if (dot > 0.5) // interior reflection
1788 f += ((dot * 2) - 1);
1789 else if (dot < -0.5) // exterior reflection
1790 f += ((dot * -2) - 1);
1792 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1793 VectorNormalize(normal);
1794 dot = DotProduct(normal, light);
1795 if (dot > 0.5) // interior reflection
1796 f += ((dot * 2) - 1);
1797 else if (dot < -0.5) // exterior reflection
1798 f += ((dot * -2) - 1);
1800 f += 16; // just to give it a haze so you can see the outline
1801 f = bound(0, f, 255);
1802 return (unsigned char) f;
1808 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1810 int basex, basey, y;
1811 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1812 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1813 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1814 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1817 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1820 float cx, cy, dx, dy, f, iradius;
1822 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1823 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1824 iradius = 1.0f / radius;
1825 alpha *= (1.0f / 255.0f);
1826 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1828 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1832 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1835 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1836 d[0] += (int)(f * (red - d[0]));
1837 d[1] += (int)(f * (green - d[1]));
1838 d[2] += (int)(f * (blue - d[2]));
1844 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1847 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1849 data[0] = bound(minr, data[0], maxr);
1850 data[1] = bound(ming, data[1], maxg);
1851 data[2] = bound(minb, data[2], maxb);
1855 void particletextureinvert(unsigned char *data)
1858 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1860 data[0] = 255 - data[0];
1861 data[1] = 255 - data[1];
1862 data[2] = 255 - data[2];
1866 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1867 static void R_InitBloodTextures (unsigned char *particletexturedata)
1870 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1873 for (i = 0;i < 8;i++)
1875 memset(&data[0][0][0], 255, sizeof(data));
1876 for (k = 0;k < 24;k++)
1877 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1878 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1879 particletextureinvert(&data[0][0][0]);
1880 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1884 for (i = 0;i < 8;i++)
1886 memset(&data[0][0][0], 255, sizeof(data));
1888 for (j = 1;j < 10;j++)
1889 for (k = min(j, m - 1);k < m;k++)
1890 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1891 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1892 particletextureinvert(&data[0][0][0]);
1893 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1898 //uncomment this to make engine save out particle font to a tga file when run
1899 //#define DUMPPARTICLEFONT
1901 static void R_InitParticleTexture (void)
1903 int x, y, d, i, k, m;
1907 // a note: decals need to modulate (multiply) the background color to
1908 // properly darken it (stain), and they need to be able to alpha fade,
1909 // this is a very difficult challenge because it means fading to white
1910 // (no change to background) rather than black (darkening everything
1911 // behind the whole decal polygon), and to accomplish this the texture is
1912 // inverted (dark red blood on white background becomes brilliant cyan
1913 // and white on black background) so we can alpha fade it to black, then
1914 // we invert it again during the blendfunc to make it work...
1916 #ifndef DUMPPARTICLEFONT
1917 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1918 if (!particlefonttexture)
1921 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1922 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1923 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1926 for (i = 0;i < 8;i++)
1928 memset(&data[0][0][0], 255, sizeof(data));
1931 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1933 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1934 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1936 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1938 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1939 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1941 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1942 d = (noise2[y][x] - 128) * 3 + 192;
1944 d = (int)(d * (1-(dx*dx+dy*dy)));
1945 d = (d * noise1[y][x]) >> 7;
1946 d = bound(0, d, 255);
1947 data[y][x][3] = (unsigned char) d;
1954 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1958 memset(&data[0][0][0], 255, sizeof(data));
1959 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1961 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1962 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1964 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1965 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1966 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1969 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1972 memset(&data[0][0][0], 255, sizeof(data));
1973 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1975 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1976 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1978 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1979 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1980 d = bound(0, d, 255);
1981 data[y][x][3] = (unsigned char) d;
1984 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1987 memset(&data[0][0][0], 255, sizeof(data));
1988 light[0] = 1;light[1] = 1;light[2] = 1;
1989 VectorNormalize(light);
1990 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1992 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1993 // stretch upper half of bubble by +50% and shrink lower half by -50%
1994 // (this gives an elongated teardrop shape)
1996 dy = (dy - 0.5f) * 2.0f;
1998 dy = (dy - 0.5f) / 1.5f;
1999 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2001 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2002 // shrink bubble width to half
2004 data[y][x][3] = shadebubble(dx, dy, light);
2007 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2010 memset(&data[0][0][0], 255, sizeof(data));
2011 light[0] = 1;light[1] = 1;light[2] = 1;
2012 VectorNormalize(light);
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 data[y][x][3] = shadebubble(dx, dy, light);
2022 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2024 // Blood particles and blood decals
2025 R_InitBloodTextures (particletexturedata);
2028 for (i = 0;i < 8;i++)
2030 memset(&data[0][0][0], 255, sizeof(data));
2031 for (k = 0;k < 12;k++)
2032 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2033 for (k = 0;k < 3;k++)
2034 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2035 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2036 particletextureinvert(&data[0][0][0]);
2037 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2040 #ifdef DUMPPARTICLEFONT
2041 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2044 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2046 Mem_Free(particletexturedata);
2048 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2050 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2051 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2052 particletexture[i].texture = particlefonttexture;
2053 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2054 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2055 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2056 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2059 #ifndef DUMPPARTICLEFONT
2060 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2061 if (!particletexture[tex_beam].texture)
2064 unsigned char noise3[64][64], data2[64][16][4];
2066 fractalnoise(&noise3[0][0], 64, 4);
2068 for (y = 0;y < 64;y++)
2070 dy = (y - 0.5f*64) / (64*0.5f-1);
2071 for (x = 0;x < 16;x++)
2073 dx = (x - 0.5f*16) / (16*0.5f-2);
2074 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2075 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2076 data2[y][x][3] = 255;
2080 #ifdef DUMPPARTICLEFONT
2081 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2083 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2085 particletexture[tex_beam].s1 = 0;
2086 particletexture[tex_beam].t1 = 0;
2087 particletexture[tex_beam].s2 = 1;
2088 particletexture[tex_beam].t2 = 1;
2091 static void r_part_start(void)
2093 particletexturepool = R_AllocTexturePool();
2094 R_InitParticleTexture ();
2095 CL_Particles_LoadEffectInfo();
2098 static void r_part_shutdown(void)
2100 R_FreeTexturePool(&particletexturepool);
2103 static void r_part_newmap(void)
2107 #define BATCHSIZE 256
2108 int particle_element3i[BATCHSIZE*6];
2109 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2111 void R_Particles_Init (void)
2114 for (i = 0;i < BATCHSIZE;i++)
2116 particle_element3i[i*6+0] = i*4+0;
2117 particle_element3i[i*6+1] = i*4+1;
2118 particle_element3i[i*6+2] = i*4+2;
2119 particle_element3i[i*6+3] = i*4+0;
2120 particle_element3i[i*6+4] = i*4+2;
2121 particle_element3i[i*6+5] = i*4+3;
2124 Cvar_RegisterVariable(&r_drawparticles);
2125 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2128 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2130 int surfacelistindex;
2131 int batchstart, batchcount;
2132 const particle_t *p;
2134 rtexture_t *texture;
2135 float *v3f, *t2f, *c4f;
2137 R_Mesh_Matrix(&identitymatrix);
2138 R_Mesh_ResetTextureState();
2139 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2140 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2141 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2142 GL_DepthMask(false);
2143 GL_DepthRange(0, 1);
2145 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2147 // first generate all the vertices at once
2148 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2150 particletexture_t *tex;
2152 float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
2154 p = cl.particles + surfacelist[surfacelistindex];
2156 blendmode = p->type->blendmode;
2158 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2159 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2160 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2161 ca = p->alpha * (1.0f / 255.0f);
2162 if (blendmode == PBLEND_MOD)
2172 ca /= cl_particles_quality.value;
2173 if (p->type->lighting)
2175 float ambient[3], diffuse[3], diffusenormal[3];
2176 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2177 cr *= (ambient[0] + 0.5 * diffuse[0]);
2178 cg *= (ambient[1] + 0.5 * diffuse[1]);
2179 cb *= (ambient[2] + 0.5 * diffuse[2]);
2181 if (r_refdef.fogenabled)
2183 fog = FogPoint_World(p->org);
2187 if (blendmode == PBLEND_ALPHA)
2190 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2191 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2192 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2195 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2196 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2197 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2198 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2200 size = p->size * cl_particles_size.value;
2202 tex = &particletexture[p->texnum];
2203 if (p->type->orientation == PARTICLE_BILLBOARD)
2205 VectorScale(r_view.left, -size, right);
2206 VectorScale(r_view.up, size, up);
2207 v3f[ 0] = org[0] - right[0] - up[0];
2208 v3f[ 1] = org[1] - right[1] - up[1];
2209 v3f[ 2] = org[2] - right[2] - up[2];
2210 v3f[ 3] = org[0] - right[0] + up[0];
2211 v3f[ 4] = org[1] - right[1] + up[1];
2212 v3f[ 5] = org[2] - right[2] + up[2];
2213 v3f[ 6] = org[0] + right[0] + up[0];
2214 v3f[ 7] = org[1] + right[1] + up[1];
2215 v3f[ 8] = org[2] + right[2] + up[2];
2216 v3f[ 9] = org[0] + right[0] - up[0];
2217 v3f[10] = org[1] + right[1] - up[1];
2218 v3f[11] = org[2] + right[2] - up[2];
2219 t2f[0] = tex->s1;t2f[1] = tex->t2;
2220 t2f[2] = tex->s1;t2f[3] = tex->t1;
2221 t2f[4] = tex->s2;t2f[5] = tex->t1;
2222 t2f[6] = tex->s2;t2f[7] = tex->t2;
2224 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2227 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2229 VectorNegate(p->vel, v);
2230 VectorVectors(v, right, up);
2233 VectorVectors(p->vel, right, up);
2234 VectorScale(right, size, right);
2235 VectorScale(up, size, up);
2236 v3f[ 0] = org[0] - right[0] - up[0];
2237 v3f[ 1] = org[1] - right[1] - up[1];
2238 v3f[ 2] = org[2] - right[2] - up[2];
2239 v3f[ 3] = org[0] - right[0] + up[0];
2240 v3f[ 4] = org[1] - right[1] + up[1];
2241 v3f[ 5] = org[2] - right[2] + up[2];
2242 v3f[ 6] = org[0] + right[0] + up[0];
2243 v3f[ 7] = org[1] + right[1] + up[1];
2244 v3f[ 8] = org[2] + right[2] + up[2];
2245 v3f[ 9] = org[0] + right[0] - up[0];
2246 v3f[10] = org[1] + right[1] - up[1];
2247 v3f[11] = org[2] + right[2] - up[2];
2248 t2f[0] = tex->s1;t2f[1] = tex->t2;
2249 t2f[2] = tex->s1;t2f[3] = tex->t1;
2250 t2f[4] = tex->s2;t2f[5] = tex->t1;
2251 t2f[6] = tex->s2;t2f[7] = tex->t2;
2253 else if (p->type->orientation == PARTICLE_SPARK)
2255 VectorMA(org, -0.02, p->vel, v);
2256 VectorMA(org, 0.02, p->vel, up2);
2257 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2258 t2f[0] = tex->s1;t2f[1] = tex->t2;
2259 t2f[2] = tex->s1;t2f[3] = tex->t1;
2260 t2f[4] = tex->s2;t2f[5] = tex->t1;
2261 t2f[6] = tex->s2;t2f[7] = tex->t2;
2263 else if (p->type->orientation == PARTICLE_BEAM)
2265 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2266 VectorSubtract(p->vel, org, up);
2267 VectorNormalize(up);
2268 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2269 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2270 t2f[0] = 1;t2f[1] = v[0];
2271 t2f[2] = 0;t2f[3] = v[0];
2272 t2f[4] = 0;t2f[5] = v[1];
2273 t2f[6] = 1;t2f[7] = v[1];
2277 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2282 // now render batches of particles based on blendmode and texture
2283 blendmode = PBLEND_ADD;
2284 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2285 texture = particletexture[63].texture;
2286 R_Mesh_TexBind(0, R_GetTexture(texture));
2287 GL_LockArrays(0, numsurfaces*4);
2290 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2292 p = cl.particles + surfacelist[surfacelistindex];
2294 if (blendmode != p->type->blendmode)
2297 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2299 batchstart = surfacelistindex;
2300 blendmode = p->type->blendmode;
2301 if (blendmode == PBLEND_ALPHA)
2302 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2303 else if (blendmode == PBLEND_ADD)
2304 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2305 else //if (blendmode == PBLEND_MOD)
2306 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2308 if (texture != particletexture[p->texnum].texture)
2311 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2313 batchstart = surfacelistindex;
2314 texture = particletexture[p->texnum].texture;
2315 R_Mesh_TexBind(0, R_GetTexture(texture));
2321 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2322 GL_LockArrays(0, 0);
2325 void R_DrawParticles (void)
2328 float minparticledist;
2331 // LordHavoc: early out conditions
2332 if ((!cl.num_particles) || (!r_drawparticles.integer))
2335 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2337 // LordHavoc: only render if not too close
2338 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2342 r_refdef.stats.particles++;
2343 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2344 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);