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;
497 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
500 if (!cl_decals.integer)
502 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);
507 p->ownermodel = cl.entities[p->owner].render.model;
508 VectorAdd(org, normal, p->org);
509 VectorCopy(normal, p->vel);
510 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
511 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
512 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
516 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
519 float bestfrac, bestorg[3], bestnormal[3];
521 int besthitent = 0, hitent;
524 for (i = 0;i < 32;i++)
527 VectorMA(org, maxdist, org2, org2);
528 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
529 // take the closest trace result that doesn't end up hitting a NOMARKS
530 // surface (sky for example)
531 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
533 bestfrac = trace.fraction;
535 VectorCopy(trace.endpos, bestorg);
536 VectorCopy(trace.plane.normal, bestnormal);
540 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
543 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
544 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
545 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)
548 matrix4x4_t tempmatrix;
549 VectorLerp(originmins, 0.5, originmaxs, center);
550 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
551 if (effectnameindex == EFFECT_SVC_PARTICLE)
553 if (cl_particles.integer)
555 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
557 CL_ParticleExplosion(center);
558 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
559 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
562 count *= cl_particles_quality.value;
563 for (;count > 0;count--)
565 int k = particlepalette[palettecolor + (rand()&7)];
566 if (cl_particles_quake.integer)
567 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);
568 else if (gamemode == GAME_GOODVSBAD2)
569 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);
571 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);
576 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
577 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
578 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
579 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
580 else if (effectnameindex == EFFECT_TE_SPIKE)
582 if (cl_particles_bulletimpacts.integer)
584 if (cl_particles_quake.integer)
586 if (cl_particles_smoke.integer)
587 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
590 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
593 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
594 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
596 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
598 if (cl_particles_bulletimpacts.integer)
600 if (cl_particles_quake.integer)
602 if (cl_particles_smoke.integer)
603 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
606 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
609 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
610 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
611 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);
613 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
615 if (cl_particles_bulletimpacts.integer)
617 if (cl_particles_quake.integer)
619 if (cl_particles_smoke.integer)
620 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
623 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
626 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
627 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
629 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
631 if (cl_particles_bulletimpacts.integer)
633 if (cl_particles_quake.integer)
635 if (cl_particles_smoke.integer)
636 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
639 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
642 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
643 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
644 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);
646 else if (effectnameindex == EFFECT_TE_BLOOD)
648 if (!cl_particles_blood.integer)
650 if (cl_particles_quake.integer)
651 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
654 static double bloodaccumulator = 0;
655 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
656 for (;bloodaccumulator > 0;bloodaccumulator--)
657 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);
660 else if (effectnameindex == EFFECT_TE_SPARK)
661 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
662 else if (effectnameindex == EFFECT_TE_PLASMABURN)
664 // plasma scorch mark
665 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
666 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
667 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
669 else if (effectnameindex == EFFECT_TE_GUNSHOT)
671 if (cl_particles_bulletimpacts.integer)
673 if (cl_particles_quake.integer)
674 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
676 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
679 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
680 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
682 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
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);
689 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
692 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
693 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
694 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);
696 else if (effectnameindex == EFFECT_TE_EXPLOSION)
698 CL_ParticleExplosion(center);
699 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);
701 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
703 CL_ParticleExplosion(center);
704 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);
706 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
708 if (cl_particles_quake.integer)
711 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
714 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);
716 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);
720 CL_ParticleExplosion(center);
721 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);
723 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
724 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);
725 else if (effectnameindex == EFFECT_TE_FLAMEJET)
727 count *= cl_particles_quality.value;
729 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);
731 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
733 float i, j, inc, vel;
736 inc = 8 / cl_particles_quality.value;
737 for (i = -128;i < 128;i += inc)
739 for (j = -128;j < 128;j += inc)
741 dir[0] = j + lhrandom(0, inc);
742 dir[1] = i + lhrandom(0, inc);
744 org[0] = center[0] + dir[0];
745 org[1] = center[1] + dir[1];
746 org[2] = center[2] + lhrandom(0, 64);
747 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
748 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);
752 else if (effectnameindex == EFFECT_TE_TELEPORT)
754 float i, j, k, inc, vel;
757 inc = 8 / cl_particles_quality.value;
758 for (i = -16;i < 16;i += inc)
760 for (j = -16;j < 16;j += inc)
762 for (k = -24;k < 32;k += inc)
764 VectorSet(dir, i*8, j*8, k*8);
765 VectorNormalize(dir);
766 vel = lhrandom(50, 113);
767 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);
771 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);
772 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);
774 else if (effectnameindex == EFFECT_TE_TEI_G3)
775 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);
776 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
778 if (cl_particles_smoke.integer)
780 count *= 0.25f * cl_particles_quality.value;
782 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);
785 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
787 CL_ParticleExplosion(center);
788 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);
790 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
793 if (cl_stainmaps.integer)
794 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
795 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
796 if (cl_particles_smoke.integer)
797 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
798 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);
799 if (cl_particles_sparks.integer)
800 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
801 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);
802 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);
804 else if (effectnameindex == EFFECT_EF_FLAME)
806 count *= 300 * cl_particles_quality.value;
808 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);
809 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);
811 else if (effectnameindex == EFFECT_EF_STARDUST)
813 count *= 200 * cl_particles_quality.value;
815 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);
816 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);
818 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
822 int smoke, blood, bubbles, r, color;
824 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
827 Vector4Set(light, 0, 0, 0, 0);
829 if (effectnameindex == EFFECT_TR_ROCKET)
830 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
831 else if (effectnameindex == EFFECT_TR_VORESPIKE)
833 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
834 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
836 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
838 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
839 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
843 matrix4x4_t tempmatrix;
844 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
845 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);
852 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
855 VectorSubtract(originmaxs, originmins, dir);
856 len = VectorNormalizeLength(dir);
859 dec = -ent->persistent.trail_time;
860 ent->persistent.trail_time += len;
861 if (ent->persistent.trail_time < 0.01f)
864 // if we skip out, leave it reset
865 ent->persistent.trail_time = 0.0f;
870 // advance into this frame to reach the first puff location
871 VectorMA(originmins, dec, dir, pos);
874 smoke = cl_particles.integer && cl_particles_smoke.integer;
875 blood = cl_particles.integer && cl_particles_blood.integer;
876 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
877 qd = 1.0f / cl_particles_quality.value;
884 if (effectnameindex == EFFECT_TR_BLOOD)
886 if (cl_particles_quake.integer)
888 color = particlepalette[67 + (rand()&3)];
889 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);
894 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);
897 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
899 if (cl_particles_quake.integer)
902 color = particlepalette[67 + (rand()&3)];
903 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);
908 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);
914 if (effectnameindex == EFFECT_TR_ROCKET)
916 if (cl_particles_quake.integer)
919 color = particlepalette[ramp3[r]];
920 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);
924 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);
925 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);
928 else if (effectnameindex == EFFECT_TR_GRENADE)
930 if (cl_particles_quake.integer)
933 color = particlepalette[ramp3[r]];
934 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);
938 particle(particletype + pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*50, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0);
941 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
943 if (cl_particles_quake.integer)
946 color = particlepalette[52 + (rand()&7)];
947 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);
948 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);
950 else if (gamemode == GAME_GOODVSBAD2)
953 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);
957 color = particlepalette[20 + (rand()&7)];
958 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);
961 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
963 if (cl_particles_quake.integer)
966 color = particlepalette[230 + (rand()&7)];
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);
968 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);
972 color = particlepalette[226 + (rand()&7)];
973 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);
976 else if (effectnameindex == EFFECT_TR_VORESPIKE)
978 if (cl_particles_quake.integer)
980 color = particlepalette[152 + (rand()&3)];
981 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);
983 else if (gamemode == GAME_GOODVSBAD2)
986 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);
988 else if (gamemode == GAME_PRYDON)
991 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);
994 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);
996 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
999 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);
1001 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1004 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);
1006 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1007 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);
1011 if (effectnameindex == EFFECT_TR_ROCKET)
1012 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);
1013 else if (effectnameindex == EFFECT_TR_GRENADE)
1014 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);
1016 // advance to next time and position
1019 VectorMA (pos, dec, dir, pos);
1022 ent->persistent.trail_time = len;
1024 else if (developer.integer >= 1)
1025 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1028 // this is also called on point effects with spawndlight = true and
1029 // spawnparticles = true
1030 // it is called CL_ParticleTrail because most code does not want to supply
1031 // these parameters, only trail handling does
1032 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)
1035 qboolean found = false;
1036 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1037 return; // invalid effect index
1038 if (!particleeffectname[effectnameindex][0])
1039 return; // no such effect
1040 VectorLerp(originmins, 0.5, originmaxs, center);
1041 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1043 int effectinfoindex;
1046 particleeffectinfo_t *info;
1048 vec3_t centervelocity;
1054 qboolean underwater;
1055 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1056 VectorLerp(originmins, 0.5, originmaxs, center);
1057 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1058 supercontents = CL_PointSuperContents(center);
1059 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1060 VectorSubtract(originmaxs, originmins, traildir);
1061 traillen = VectorLength(traildir);
1062 VectorNormalize(traildir);
1063 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1065 if (info->effectnameindex == effectnameindex)
1068 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1070 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1073 // spawn a dlight if requested
1074 if (info->lightradiusstart > 0 && spawndlight)
1076 matrix4x4_t tempmatrix;
1077 if (info->trailspacing > 0)
1078 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1080 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1081 if (info->lighttime > 0 && info->lightradiusfade > 0)
1083 // light flash (explosion, etc)
1084 // called when effect starts
1085 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);
1090 // called by CL_LinkNetworkEntity
1091 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1092 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);
1096 if (!spawnparticles)
1101 if (info->tex[1] > info->tex[0])
1103 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1104 tex = min(tex, info->tex[1] - 1);
1106 if (info->particletype == pt_decal)
1107 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]);
1108 else if (info->particletype == pt_beam)
1109 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);
1112 if (!cl_particles.integer)
1114 switch (info->particletype)
1116 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1117 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1118 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1119 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1120 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1121 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1124 VectorCopy(originmins, trailpos);
1125 if (info->trailspacing > 0)
1127 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1128 trailstep = info->trailspacing / cl_particles_quality.value;
1132 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1135 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1136 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1138 if (info->tex[1] > info->tex[0])
1140 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1141 tex = min(tex, info->tex[1] - 1);
1145 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1146 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1147 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1150 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);
1152 VectorMA(trailpos, trailstep, traildir, trailpos);
1159 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1162 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)
1164 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1172 void CL_EntityParticles (const entity_t *ent)
1175 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1176 static vec3_t avelocities[NUMVERTEXNORMALS];
1177 if (!cl_particles.integer) return;
1179 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1181 if (!avelocities[0][0])
1182 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1183 avelocities[0][i] = lhrandom(0, 2.55);
1185 for (i = 0;i < NUMVERTEXNORMALS;i++)
1187 yaw = cl.time * avelocities[i][0];
1188 pitch = cl.time * avelocities[i][1];
1189 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1190 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1191 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1192 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);
1197 void CL_ReadPointFile_f (void)
1199 vec3_t org, leakorg;
1201 char *pointfile = NULL, *pointfilepos, *t, tchar;
1202 char name[MAX_OSPATH];
1207 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1208 strlcat (name, ".pts", sizeof (name));
1209 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1212 Con_Printf("Could not open %s\n", name);
1216 Con_Printf("Reading %s...\n", name);
1217 VectorClear(leakorg);
1220 pointfilepos = pointfile;
1221 while (*pointfilepos)
1223 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1228 while (*t && *t != '\n' && *t != '\r')
1232 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1238 VectorCopy(org, leakorg);
1241 if (cl.num_particles < cl.max_particles - 3)
1244 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);
1247 Mem_Free(pointfile);
1248 VectorCopy(leakorg, org);
1249 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1251 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);
1252 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);
1253 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);
1258 CL_ParseParticleEffect
1260 Parse an effect out of the server message
1263 void CL_ParseParticleEffect (void)
1266 int i, count, msgcount, color;
1268 MSG_ReadVector(org, cls.protocol);
1269 for (i=0 ; i<3 ; i++)
1270 dir[i] = MSG_ReadChar ();
1271 msgcount = MSG_ReadByte ();
1272 color = MSG_ReadByte ();
1274 if (msgcount == 255)
1279 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1284 CL_ParticleExplosion
1288 void CL_ParticleExplosion (const vec3_t org)
1294 if (cl_stainmaps.integer)
1295 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1296 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1298 if (cl_particles_quake.integer)
1300 for (i = 0;i < 1024;i++)
1306 color = particlepalette[ramp1[r]];
1307 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);
1311 color = particlepalette[ramp2[r]];
1312 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);
1318 i = CL_PointSuperContents(org);
1319 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1321 if (cl_particles.integer && cl_particles_bubbles.integer)
1322 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1323 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);
1327 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1329 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1331 for (i = 0;i < 32;i++)
1335 for (k = 0;k < 16;k++)
1337 v[0] = org[0] + lhrandom(-48, 48);
1338 v[1] = org[1] + lhrandom(-48, 48);
1339 v[2] = org[2] + lhrandom(-48, 48);
1340 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1341 if (trace.fraction >= 0.1)
1344 VectorSubtract(trace.endpos, org, v2);
1345 VectorScale(v2, 2.0f, v2);
1346 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);
1350 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1352 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1356 for (k = 0;k < 16;k++)
1359 VectorMA(org, 128, v2, v);
1360 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1361 if (trace.fraction >= 0.1)
1364 VectorSubtract(trace.endpos, org, v2);
1365 VectorScale(v2, 2.0f, v2);
1366 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);
1372 if (cl_particles_explosions_shell.integer)
1373 R_NewExplosion(org);
1378 CL_ParticleExplosion2
1382 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1385 if (!cl_particles.integer) return;
1387 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1389 k = particlepalette[colorStart + (i % colorLength)];
1390 if (cl_particles_quake.integer)
1391 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);
1393 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);
1397 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1399 if (cl_particles_sparks.integer)
1401 sparkcount *= cl_particles_quality.value;
1402 while(sparkcount-- > 0)
1403 particle(particletype + pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.4f, 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.1, 0, 0, 0, 64);
1407 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1409 if (cl_particles_smoke.integer)
1411 smokecount *= cl_particles_quality.value;
1412 while(smokecount-- > 0)
1413 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);
1417 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)
1420 if (!cl_particles.integer) return;
1422 count = (int)(count * cl_particles_quality.value);
1425 k = particlepalette[colorbase + (rand()&3)];
1426 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);
1430 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1433 float z, minz, maxz;
1435 if (!cl_particles.integer) return;
1436 if (dir[2] < 0) // falling
1441 minz = z - fabs(dir[2]) * 0.1;
1442 maxz = z + fabs(dir[2]) * 0.1;
1443 minz = bound(mins[2], minz, maxs[2]);
1444 maxz = bound(mins[2], maxz, maxs[2]);
1446 count = (int)(count * cl_particles_quality.value);
1451 if (!cl_particles_rain.integer) break;
1452 count *= 4; // ick, this should be in the mod or maps?
1456 k = particlepalette[colorbase + (rand()&3)];
1457 if (gamemode == GAME_GOODVSBAD2)
1458 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);
1460 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);
1464 if (!cl_particles_snow.integer) break;
1467 k = particlepalette[colorbase + (rand()&3)];
1468 if (gamemode == GAME_GOODVSBAD2)
1469 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);
1471 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);
1473 VectorCopy(p->vel, p->relativedirection);
1477 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1486 void CL_MoveParticles (void)
1489 int i, maxparticle, j, a, content;
1490 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1491 particletype_t *decaltype, *bloodtype;
1495 // LordHavoc: early out condition
1496 if (!cl.num_particles)
1498 cl.free_particle = 0;
1502 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1503 gravity = frametime * sv_gravity.value;
1504 dvel = 1+4*frametime;
1505 decalfade = frametime * 255 / cl_decals_fadetime.value;
1506 decaltype = particletype + pt_decal;
1507 bloodtype = particletype + pt_blood;
1511 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1515 if (cl.free_particle > i)
1516 cl.free_particle = i;
1521 // heavily optimized decal case
1522 if (p->type == decaltype)
1524 // FIXME: this has fairly wacky handling of alpha
1525 if (cl.time > p->time2 + cl_decals_time.value)
1527 p->alpha -= decalfade;
1531 if (cl.free_particle > i)
1532 cl.free_particle = i;
1538 if (cl.entities[p->owner].render.model == p->ownermodel)
1540 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1541 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1546 if (cl.free_particle > i)
1547 cl.free_particle = i;
1555 p->size += p->sizeincrease * frametime;
1556 p->alpha -= p->alphafade * frametime;
1561 if (cl.free_particle > i)
1562 cl.free_particle = i;
1566 if (p->type->orientation != PARTICLE_BEAM)
1568 VectorCopy(p->org, oldorg);
1569 VectorMA(p->org, frametime, p->vel, p->org);
1570 VectorCopy(p->org, org);
1573 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);
1574 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1575 // or if the trace hit something flagged as NOIMPACT
1576 // then remove the particle
1577 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1582 // react if the particle hit something
1583 if (trace.fraction < 1)
1585 VectorCopy(trace.endpos, p->org);
1586 if (p->type == particletype + pt_rain)
1588 // raindrop - splash on solid/water/slime/lava
1590 // convert from a raindrop particle to a rainsplash decal
1591 VectorCopy(trace.plane.normal, p->vel);
1592 VectorAdd(p->org, p->vel, p->org);
1593 p->type = particletype + pt_raindecal;
1594 p->texnum = tex_rainsplash;
1596 p->alphafade = p->alpha / 0.4;
1599 p->liquidfriction = 0;
1602 p->sizeincrease = p->size * 2;
1605 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);
1607 else if (p->type == bloodtype)
1609 // blood - splash on solid
1610 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1615 if (cl_stainmaps.integer)
1616 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)));
1617 if (!cl_decals.integer)
1622 // convert from a blood particle to a blood decal
1623 VectorCopy(trace.plane.normal, p->vel);
1624 VectorAdd(p->org, p->vel, p->org);
1626 p->type = particletype + pt_decal;
1627 p->texnum = tex_blooddecal[rand()&7];
1629 p->ownermodel = cl.entities[hitent].render.model;
1630 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1631 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1632 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1637 p->liquidfriction = 0;
1641 else if (p->bounce < 0)
1643 // bounce -1 means remove on impact
1649 // anything else - bounce off solid
1650 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1651 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1652 if (DotProduct(p->vel, p->vel) < 0.03)
1653 VectorClear(p->vel);
1657 p->vel[2] -= p->gravity * gravity;
1659 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1661 f = 1.0f - min(p->liquidfriction * frametime, 1);
1662 VectorScale(p->vel, f, p->vel);
1664 else if (p->airfriction)
1666 f = 1.0f - min(p->airfriction * frametime, 1);
1667 VectorScale(p->vel, f, p->vel);
1671 if (p->type != particletype + pt_static)
1673 switch (p->type - particletype)
1675 case pt_entityparticle:
1676 // particle that removes itself after one rendered frame
1683 a = CL_PointSuperContents(p->org);
1684 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1686 p->size += frametime * 8;
1687 //p->alpha -= bloodwaterfade;
1690 p->vel[2] -= gravity;
1691 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1695 a = CL_PointSuperContents(p->org);
1696 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1703 a = CL_PointSuperContents(p->org);
1704 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1708 if (cl.time > p->time2)
1711 p->time2 = cl.time + (rand() & 3) * 0.1;
1712 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1713 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1714 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1716 a = CL_PointSuperContents(p->org);
1717 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1725 cl.num_particles = maxparticle + 1;
1728 #define MAX_PARTICLETEXTURES 64
1729 // particletexture_t is a rectangle in the particlefonttexture
1730 typedef struct particletexture_s
1732 rtexture_t *texture;
1733 float s1, t1, s2, t2;
1737 static rtexturepool_t *particletexturepool;
1738 static rtexture_t *particlefonttexture;
1739 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1741 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1743 #define PARTICLETEXTURESIZE 64
1744 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1746 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1750 dz = 1 - (dx*dx+dy*dy);
1751 if (dz > 0) // it does hit the sphere
1755 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1756 VectorNormalize(normal);
1757 dot = DotProduct(normal, light);
1758 if (dot > 0.5) // interior reflection
1759 f += ((dot * 2) - 1);
1760 else if (dot < -0.5) // exterior reflection
1761 f += ((dot * -2) - 1);
1763 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1764 VectorNormalize(normal);
1765 dot = DotProduct(normal, light);
1766 if (dot > 0.5) // interior reflection
1767 f += ((dot * 2) - 1);
1768 else if (dot < -0.5) // exterior reflection
1769 f += ((dot * -2) - 1);
1771 f += 16; // just to give it a haze so you can see the outline
1772 f = bound(0, f, 255);
1773 return (unsigned char) f;
1779 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1781 int basex, basey, y;
1782 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1783 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1784 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1785 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1788 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1791 float cx, cy, dx, dy, f, iradius;
1793 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1794 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1795 iradius = 1.0f / radius;
1796 alpha *= (1.0f / 255.0f);
1797 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1799 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1803 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1806 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1807 d[0] += (int)(f * (red - d[0]));
1808 d[1] += (int)(f * (green - d[1]));
1809 d[2] += (int)(f * (blue - d[2]));
1815 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1818 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1820 data[0] = bound(minr, data[0], maxr);
1821 data[1] = bound(ming, data[1], maxg);
1822 data[2] = bound(minb, data[2], maxb);
1826 void particletextureinvert(unsigned char *data)
1829 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1831 data[0] = 255 - data[0];
1832 data[1] = 255 - data[1];
1833 data[2] = 255 - data[2];
1837 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1838 static void R_InitBloodTextures (unsigned char *particletexturedata)
1841 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1844 for (i = 0;i < 8;i++)
1846 memset(&data[0][0][0], 255, sizeof(data));
1847 for (k = 0;k < 24;k++)
1848 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1849 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1850 particletextureinvert(&data[0][0][0]);
1851 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1855 for (i = 0;i < 8;i++)
1857 memset(&data[0][0][0], 255, sizeof(data));
1859 for (j = 1;j < 10;j++)
1860 for (k = min(j, m - 1);k < m;k++)
1861 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1862 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1863 particletextureinvert(&data[0][0][0]);
1864 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1869 //uncomment this to make engine save out particle font to a tga file when run
1870 //#define DUMPPARTICLEFONT
1872 static void R_InitParticleTexture (void)
1874 int x, y, d, i, k, m;
1878 // a note: decals need to modulate (multiply) the background color to
1879 // properly darken it (stain), and they need to be able to alpha fade,
1880 // this is a very difficult challenge because it means fading to white
1881 // (no change to background) rather than black (darkening everything
1882 // behind the whole decal polygon), and to accomplish this the texture is
1883 // inverted (dark red blood on white background becomes brilliant cyan
1884 // and white on black background) so we can alpha fade it to black, then
1885 // we invert it again during the blendfunc to make it work...
1887 #ifndef DUMPPARTICLEFONT
1888 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1889 if (!particlefonttexture)
1892 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1893 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1894 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1897 for (i = 0;i < 8;i++)
1899 memset(&data[0][0][0], 255, sizeof(data));
1902 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1904 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1905 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1907 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1909 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1910 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1912 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1913 d = (noise2[y][x] - 128) * 3 + 192;
1915 d = (int)(d * (1-(dx*dx+dy*dy)));
1916 d = (d * noise1[y][x]) >> 7;
1917 d = bound(0, d, 255);
1918 data[y][x][3] = (unsigned char) d;
1925 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1929 memset(&data[0][0][0], 255, sizeof(data));
1930 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1932 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1933 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1935 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1936 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1937 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1940 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1943 memset(&data[0][0][0], 255, sizeof(data));
1944 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1946 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1947 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1949 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1950 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1951 d = bound(0, d, 255);
1952 data[y][x][3] = (unsigned char) d;
1955 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1958 memset(&data[0][0][0], 255, sizeof(data));
1959 light[0] = 1;light[1] = 1;light[2] = 1;
1960 VectorNormalize(light);
1961 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1963 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1964 // stretch upper half of bubble by +50% and shrink lower half by -50%
1965 // (this gives an elongated teardrop shape)
1967 dy = (dy - 0.5f) * 2.0f;
1969 dy = (dy - 0.5f) / 1.5f;
1970 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1972 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1973 // shrink bubble width to half
1975 data[y][x][3] = shadebubble(dx, dy, light);
1978 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1981 memset(&data[0][0][0], 255, sizeof(data));
1982 light[0] = 1;light[1] = 1;light[2] = 1;
1983 VectorNormalize(light);
1984 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1986 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1987 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1989 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1990 data[y][x][3] = shadebubble(dx, dy, light);
1993 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1995 // Blood particles and blood decals
1996 R_InitBloodTextures (particletexturedata);
1999 for (i = 0;i < 8;i++)
2001 memset(&data[0][0][0], 255, sizeof(data));
2002 for (k = 0;k < 12;k++)
2003 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2004 for (k = 0;k < 3;k++)
2005 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2006 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2007 particletextureinvert(&data[0][0][0]);
2008 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2011 #ifdef DUMPPARTICLEFONT
2012 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2015 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2017 Mem_Free(particletexturedata);
2019 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2021 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2022 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2023 particletexture[i].texture = particlefonttexture;
2024 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2025 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2026 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2027 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2030 #ifndef DUMPPARTICLEFONT
2031 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2032 if (!particletexture[tex_beam].texture)
2035 unsigned char noise3[64][64], data2[64][16][4];
2037 fractalnoise(&noise3[0][0], 64, 4);
2039 for (y = 0;y < 64;y++)
2041 dy = (y - 0.5f*64) / (64*0.5f-1);
2042 for (x = 0;x < 16;x++)
2044 dx = (x - 0.5f*16) / (16*0.5f-2);
2045 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2046 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2047 data2[y][x][3] = 255;
2051 #ifdef DUMPPARTICLEFONT
2052 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2054 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2056 particletexture[tex_beam].s1 = 0;
2057 particletexture[tex_beam].t1 = 0;
2058 particletexture[tex_beam].s2 = 1;
2059 particletexture[tex_beam].t2 = 1;
2062 static void r_part_start(void)
2064 particletexturepool = R_AllocTexturePool();
2065 R_InitParticleTexture ();
2066 CL_Particles_LoadEffectInfo();
2069 static void r_part_shutdown(void)
2071 R_FreeTexturePool(&particletexturepool);
2074 static void r_part_newmap(void)
2078 #define BATCHSIZE 256
2079 int particle_element3i[BATCHSIZE*6];
2080 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2082 void R_Particles_Init (void)
2085 for (i = 0;i < BATCHSIZE;i++)
2087 particle_element3i[i*6+0] = i*4+0;
2088 particle_element3i[i*6+1] = i*4+1;
2089 particle_element3i[i*6+2] = i*4+2;
2090 particle_element3i[i*6+3] = i*4+0;
2091 particle_element3i[i*6+4] = i*4+2;
2092 particle_element3i[i*6+5] = i*4+3;
2095 Cvar_RegisterVariable(&r_drawparticles);
2096 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2099 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2101 int surfacelistindex;
2102 int batchstart, batchcount;
2103 const particle_t *p;
2105 rtexture_t *texture;
2106 float *v3f, *t2f, *c4f;
2108 R_Mesh_Matrix(&identitymatrix);
2109 R_Mesh_ResetTextureState();
2110 R_Mesh_VertexPointer(particle_vertex3f);
2111 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2112 R_Mesh_ColorPointer(particle_color4f);
2113 GL_DepthMask(false);
2115 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2117 // first generate all the vertices at once
2118 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2120 particletexture_t *tex;
2122 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2124 p = cl.particles + surfacelist[surfacelistindex];
2126 blendmode = p->type->blendmode;
2128 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2129 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2130 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2131 ca = p->alpha * (1.0f / 255.0f);
2132 if (blendmode == PBLEND_MOD)
2142 ca /= cl_particles_quality.value;
2143 if (p->type->lighting)
2145 float ambient[3], diffuse[3], diffusenormal[3];
2146 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2147 cr *= (ambient[0] + 0.5 * diffuse[0]);
2148 cg *= (ambient[1] + 0.5 * diffuse[1]);
2149 cb *= (ambient[2] + 0.5 * diffuse[2]);
2151 if (r_refdef.fogenabled)
2153 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2158 if (blendmode == PBLEND_ALPHA)
2160 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2161 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2162 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2165 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2166 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2167 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2168 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2170 size = p->size * cl_particles_size.value;
2172 tex = &particletexture[p->texnum];
2173 if (p->type->orientation == PARTICLE_BILLBOARD)
2175 VectorScale(r_view.left, -size, right);
2176 VectorScale(r_view.up, size, up);
2177 v3f[ 0] = org[0] - right[0] - up[0];
2178 v3f[ 1] = org[1] - right[1] - up[1];
2179 v3f[ 2] = org[2] - right[2] - up[2];
2180 v3f[ 3] = org[0] - right[0] + up[0];
2181 v3f[ 4] = org[1] - right[1] + up[1];
2182 v3f[ 5] = org[2] - right[2] + up[2];
2183 v3f[ 6] = org[0] + right[0] + up[0];
2184 v3f[ 7] = org[1] + right[1] + up[1];
2185 v3f[ 8] = org[2] + right[2] + up[2];
2186 v3f[ 9] = org[0] + right[0] - up[0];
2187 v3f[10] = org[1] + right[1] - up[1];
2188 v3f[11] = org[2] + right[2] - up[2];
2189 t2f[0] = tex->s1;t2f[1] = tex->t2;
2190 t2f[2] = tex->s1;t2f[3] = tex->t1;
2191 t2f[4] = tex->s2;t2f[5] = tex->t1;
2192 t2f[6] = tex->s2;t2f[7] = tex->t2;
2194 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2197 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2199 VectorNegate(p->vel, v);
2200 VectorVectors(v, right, up);
2203 VectorVectors(p->vel, right, up);
2204 VectorScale(right, size, right);
2205 VectorScale(up, size, up);
2206 v3f[ 0] = org[0] - right[0] - up[0];
2207 v3f[ 1] = org[1] - right[1] - up[1];
2208 v3f[ 2] = org[2] - right[2] - up[2];
2209 v3f[ 3] = org[0] - right[0] + up[0];
2210 v3f[ 4] = org[1] - right[1] + up[1];
2211 v3f[ 5] = org[2] - right[2] + up[2];
2212 v3f[ 6] = org[0] + right[0] + up[0];
2213 v3f[ 7] = org[1] + right[1] + up[1];
2214 v3f[ 8] = org[2] + right[2] + up[2];
2215 v3f[ 9] = org[0] + right[0] - up[0];
2216 v3f[10] = org[1] + right[1] - up[1];
2217 v3f[11] = org[2] + right[2] - up[2];
2218 t2f[0] = tex->s1;t2f[1] = tex->t2;
2219 t2f[2] = tex->s1;t2f[3] = tex->t1;
2220 t2f[4] = tex->s2;t2f[5] = tex->t1;
2221 t2f[6] = tex->s2;t2f[7] = tex->t2;
2223 else if (p->type->orientation == PARTICLE_SPARK)
2225 VectorMA(org, -0.02, p->vel, v);
2226 VectorMA(org, 0.02, p->vel, up2);
2227 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2228 t2f[0] = tex->s1;t2f[1] = tex->t2;
2229 t2f[2] = tex->s1;t2f[3] = tex->t1;
2230 t2f[4] = tex->s2;t2f[5] = tex->t1;
2231 t2f[6] = tex->s2;t2f[7] = tex->t2;
2233 else if (p->type->orientation == PARTICLE_BEAM)
2235 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2236 VectorSubtract(p->vel, org, up);
2237 VectorNormalize(up);
2238 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2239 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2240 t2f[0] = 1;t2f[1] = v[0];
2241 t2f[2] = 0;t2f[3] = v[0];
2242 t2f[4] = 0;t2f[5] = v[1];
2243 t2f[6] = 1;t2f[7] = v[1];
2247 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2252 // now render batches of particles based on blendmode and texture
2253 blendmode = PBLEND_ADD;
2254 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2255 texture = particletexture[63].texture;
2256 R_Mesh_TexBind(0, R_GetTexture(texture));
2257 GL_LockArrays(0, numsurfaces*4);
2260 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2262 p = cl.particles + surfacelist[surfacelistindex];
2264 if (blendmode != p->type->blendmode)
2267 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2269 batchstart = surfacelistindex;
2270 blendmode = p->type->blendmode;
2271 if (blendmode == PBLEND_ALPHA)
2272 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2273 else if (blendmode == PBLEND_ADD)
2274 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2275 else //if (blendmode == PBLEND_MOD)
2276 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2278 if (texture != particletexture[p->texnum].texture)
2281 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2283 batchstart = surfacelistindex;
2284 texture = particletexture[p->texnum].texture;
2285 R_Mesh_TexBind(0, R_GetTexture(texture));
2291 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2292 GL_LockArrays(0, 0);
2295 void R_DrawParticles (void)
2298 float minparticledist;
2301 // LordHavoc: early out conditions
2302 if ((!cl.num_particles) || (!r_drawparticles.integer))
2305 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2307 // LordHavoc: only render if not too close
2308 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2312 r_refdef.stats.particles++;
2313 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2314 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);