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);
591 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
592 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
596 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
597 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
599 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
601 if (cl_particles_bulletimpacts.integer)
603 if (cl_particles_quake.integer)
605 if (cl_particles_smoke.integer)
606 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
610 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
611 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
615 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
616 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
617 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);
619 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
621 if (cl_particles_bulletimpacts.integer)
623 if (cl_particles_quake.integer)
625 if (cl_particles_smoke.integer)
626 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
630 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
631 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
635 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
636 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
638 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
640 if (cl_particles_bulletimpacts.integer)
642 if (cl_particles_quake.integer)
644 if (cl_particles_smoke.integer)
645 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
649 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
650 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
654 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
655 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
656 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);
658 else if (effectnameindex == EFFECT_TE_BLOOD)
660 if (!cl_particles_blood.integer)
662 if (cl_particles_quake.integer)
663 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
666 static double bloodaccumulator = 0;
667 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
668 for (;bloodaccumulator > 0;bloodaccumulator--)
669 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);
672 else if (effectnameindex == EFFECT_TE_SPARK)
673 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
674 else if (effectnameindex == EFFECT_TE_PLASMABURN)
676 // plasma scorch mark
677 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
678 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
679 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
681 else if (effectnameindex == EFFECT_TE_GUNSHOT)
683 if (cl_particles_bulletimpacts.integer)
685 if (cl_particles_quake.integer)
686 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
689 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
690 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
694 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
695 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
697 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
699 if (cl_particles_bulletimpacts.integer)
701 if (cl_particles_quake.integer)
702 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
705 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
706 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
710 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
711 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
712 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);
714 else if (effectnameindex == EFFECT_TE_EXPLOSION)
716 CL_ParticleExplosion(center);
717 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);
719 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
721 CL_ParticleExplosion(center);
722 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);
724 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
726 if (cl_particles_quake.integer)
729 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
732 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);
734 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);
738 CL_ParticleExplosion(center);
739 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);
741 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
742 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);
743 else if (effectnameindex == EFFECT_TE_FLAMEJET)
745 count *= cl_particles_quality.value;
747 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);
749 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
751 float i, j, inc, vel;
754 inc = 8 / cl_particles_quality.value;
755 for (i = -128;i < 128;i += inc)
757 for (j = -128;j < 128;j += inc)
759 dir[0] = j + lhrandom(0, inc);
760 dir[1] = i + lhrandom(0, inc);
762 org[0] = center[0] + dir[0];
763 org[1] = center[1] + dir[1];
764 org[2] = center[2] + lhrandom(0, 64);
765 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
766 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);
770 else if (effectnameindex == EFFECT_TE_TELEPORT)
772 float i, j, k, inc, vel;
775 inc = 8 / cl_particles_quality.value;
776 for (i = -16;i < 16;i += inc)
778 for (j = -16;j < 16;j += inc)
780 for (k = -24;k < 32;k += inc)
782 VectorSet(dir, i*8, j*8, k*8);
783 VectorNormalize(dir);
784 vel = lhrandom(50, 113);
785 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);
789 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);
790 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);
792 else if (effectnameindex == EFFECT_TE_TEI_G3)
793 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);
794 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
796 if (cl_particles_smoke.integer)
798 count *= 0.25f * cl_particles_quality.value;
800 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);
803 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
805 CL_ParticleExplosion(center);
806 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);
808 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
811 if (cl_stainmaps.integer)
812 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
813 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
814 if (cl_particles_smoke.integer)
815 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
816 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);
817 if (cl_particles_sparks.integer)
818 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
819 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);
820 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);
822 else if (effectnameindex == EFFECT_EF_FLAME)
824 count *= 300 * cl_particles_quality.value;
826 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);
827 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);
829 else if (effectnameindex == EFFECT_EF_STARDUST)
831 count *= 200 * cl_particles_quality.value;
833 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);
834 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);
836 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
840 int smoke, blood, bubbles, r, color;
842 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
845 Vector4Set(light, 0, 0, 0, 0);
847 if (effectnameindex == EFFECT_TR_ROCKET)
848 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
849 else if (effectnameindex == EFFECT_TR_VORESPIKE)
851 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
852 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
854 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
856 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
857 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
861 matrix4x4_t tempmatrix;
862 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
863 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);
870 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
873 VectorSubtract(originmaxs, originmins, dir);
874 len = VectorNormalizeLength(dir);
877 dec = -ent->persistent.trail_time;
878 ent->persistent.trail_time += len;
879 if (ent->persistent.trail_time < 0.01f)
882 // if we skip out, leave it reset
883 ent->persistent.trail_time = 0.0f;
888 // advance into this frame to reach the first puff location
889 VectorMA(originmins, dec, dir, pos);
892 smoke = cl_particles.integer && cl_particles_smoke.integer;
893 blood = cl_particles.integer && cl_particles_blood.integer;
894 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
895 qd = 1.0f / cl_particles_quality.value;
902 if (effectnameindex == EFFECT_TR_BLOOD)
904 if (cl_particles_quake.integer)
906 color = particlepalette[67 + (rand()&3)];
907 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);
912 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);
915 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
917 if (cl_particles_quake.integer)
920 color = particlepalette[67 + (rand()&3)];
921 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);
926 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);
932 if (effectnameindex == EFFECT_TR_ROCKET)
934 if (cl_particles_quake.integer)
937 color = particlepalette[ramp3[r]];
938 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);
942 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);
943 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);
946 else if (effectnameindex == EFFECT_TR_GRENADE)
948 if (cl_particles_quake.integer)
951 color = particlepalette[ramp3[r]];
952 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);
956 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);
959 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
961 if (cl_particles_quake.integer)
964 color = particlepalette[52 + (rand()&7)];
965 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);
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);
968 else if (gamemode == GAME_GOODVSBAD2)
971 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);
975 color = particlepalette[20 + (rand()&7)];
976 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);
979 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
981 if (cl_particles_quake.integer)
984 color = particlepalette[230 + (rand()&7)];
985 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);
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);
990 color = particlepalette[226 + (rand()&7)];
991 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);
994 else if (effectnameindex == EFFECT_TR_VORESPIKE)
996 if (cl_particles_quake.integer)
998 color = particlepalette[152 + (rand()&3)];
999 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);
1001 else if (gamemode == GAME_GOODVSBAD2)
1004 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);
1006 else if (gamemode == GAME_PRYDON)
1009 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);
1012 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);
1014 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1017 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);
1019 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1022 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);
1024 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1025 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);
1029 if (effectnameindex == EFFECT_TR_ROCKET)
1030 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);
1031 else if (effectnameindex == EFFECT_TR_GRENADE)
1032 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);
1034 // advance to next time and position
1037 VectorMA (pos, dec, dir, pos);
1040 ent->persistent.trail_time = len;
1042 else if (developer.integer >= 1)
1043 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1046 // this is also called on point effects with spawndlight = true and
1047 // spawnparticles = true
1048 // it is called CL_ParticleTrail because most code does not want to supply
1049 // these parameters, only trail handling does
1050 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)
1053 qboolean found = false;
1054 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1055 return; // invalid effect index
1056 if (!particleeffectname[effectnameindex][0])
1057 return; // no such effect
1058 VectorLerp(originmins, 0.5, originmaxs, center);
1059 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1061 int effectinfoindex;
1064 particleeffectinfo_t *info;
1066 vec3_t centervelocity;
1072 qboolean underwater;
1073 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1074 VectorLerp(originmins, 0.5, originmaxs, center);
1075 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1076 supercontents = CL_PointSuperContents(center);
1077 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1078 VectorSubtract(originmaxs, originmins, traildir);
1079 traillen = VectorLength(traildir);
1080 VectorNormalize(traildir);
1081 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1083 if (info->effectnameindex == effectnameindex)
1086 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1088 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1091 // spawn a dlight if requested
1092 if (info->lightradiusstart > 0 && spawndlight)
1094 matrix4x4_t tempmatrix;
1095 if (info->trailspacing > 0)
1096 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1098 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1099 if (info->lighttime > 0 && info->lightradiusfade > 0)
1101 // light flash (explosion, etc)
1102 // called when effect starts
1103 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);
1108 // called by CL_LinkNetworkEntity
1109 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1110 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);
1114 if (!spawnparticles)
1119 if (info->tex[1] > info->tex[0])
1121 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1122 tex = min(tex, info->tex[1] - 1);
1124 if (info->particletype == pt_decal)
1125 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]);
1126 else if (info->particletype == pt_beam)
1127 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);
1130 if (!cl_particles.integer)
1132 switch (info->particletype)
1134 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1135 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1136 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1137 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1138 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1139 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1142 VectorCopy(originmins, trailpos);
1143 if (info->trailspacing > 0)
1145 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1146 trailstep = info->trailspacing / cl_particles_quality.value;
1150 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1153 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1154 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1156 if (info->tex[1] > info->tex[0])
1158 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1159 tex = min(tex, info->tex[1] - 1);
1163 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1164 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1165 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1168 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);
1170 VectorMA(trailpos, trailstep, traildir, trailpos);
1177 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1180 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)
1182 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1190 void CL_EntityParticles (const entity_t *ent)
1193 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1194 static vec3_t avelocities[NUMVERTEXNORMALS];
1195 if (!cl_particles.integer) return;
1197 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1199 if (!avelocities[0][0])
1200 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1201 avelocities[0][i] = lhrandom(0, 2.55);
1203 for (i = 0;i < NUMVERTEXNORMALS;i++)
1205 yaw = cl.time * avelocities[i][0];
1206 pitch = cl.time * avelocities[i][1];
1207 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1208 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1209 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1210 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);
1215 void CL_ReadPointFile_f (void)
1217 vec3_t org, leakorg;
1219 char *pointfile = NULL, *pointfilepos, *t, tchar;
1220 char name[MAX_OSPATH];
1225 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1226 strlcat (name, ".pts", sizeof (name));
1227 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1230 Con_Printf("Could not open %s\n", name);
1234 Con_Printf("Reading %s...\n", name);
1235 VectorClear(leakorg);
1238 pointfilepos = pointfile;
1239 while (*pointfilepos)
1241 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1246 while (*t && *t != '\n' && *t != '\r')
1250 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1256 VectorCopy(org, leakorg);
1259 if (cl.num_particles < cl.max_particles - 3)
1262 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);
1265 Mem_Free(pointfile);
1266 VectorCopy(leakorg, org);
1267 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1269 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);
1270 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);
1271 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);
1276 CL_ParseParticleEffect
1278 Parse an effect out of the server message
1281 void CL_ParseParticleEffect (void)
1284 int i, count, msgcount, color;
1286 MSG_ReadVector(org, cls.protocol);
1287 for (i=0 ; i<3 ; i++)
1288 dir[i] = MSG_ReadChar ();
1289 msgcount = MSG_ReadByte ();
1290 color = MSG_ReadByte ();
1292 if (msgcount == 255)
1297 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1302 CL_ParticleExplosion
1306 void CL_ParticleExplosion (const vec3_t org)
1312 if (cl_stainmaps.integer)
1313 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1314 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1316 if (cl_particles_quake.integer)
1318 for (i = 0;i < 1024;i++)
1324 color = particlepalette[ramp1[r]];
1325 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);
1329 color = particlepalette[ramp2[r]];
1330 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);
1336 i = CL_PointSuperContents(org);
1337 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1339 if (cl_particles.integer && cl_particles_bubbles.integer)
1340 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1341 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);
1345 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1347 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1349 for (i = 0;i < 32;i++)
1353 for (k = 0;k < 16;k++)
1355 v[0] = org[0] + lhrandom(-48, 48);
1356 v[1] = org[1] + lhrandom(-48, 48);
1357 v[2] = org[2] + lhrandom(-48, 48);
1358 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1359 if (trace.fraction >= 0.1)
1362 VectorSubtract(trace.endpos, org, v2);
1363 VectorScale(v2, 2.0f, v2);
1364 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);
1368 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1370 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1374 for (k = 0;k < 16;k++)
1377 VectorMA(org, 128, v2, v);
1378 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1379 if (trace.fraction >= 0.1)
1382 VectorSubtract(trace.endpos, org, v2);
1383 VectorScale(v2, 2.0f, v2);
1384 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);
1390 if (cl_particles_explosions_shell.integer)
1391 R_NewExplosion(org);
1396 CL_ParticleExplosion2
1400 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1403 if (!cl_particles.integer) return;
1405 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1407 k = particlepalette[colorStart + (i % colorLength)];
1408 if (cl_particles_quake.integer)
1409 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);
1411 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);
1415 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1417 if (cl_particles_sparks.integer)
1419 sparkcount *= cl_particles_quality.value;
1420 while(sparkcount-- > 0)
1421 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);
1425 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1427 if (cl_particles_smoke.integer)
1429 smokecount *= cl_particles_quality.value;
1430 while(smokecount-- > 0)
1431 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);
1435 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)
1438 if (!cl_particles.integer) return;
1440 count = (int)(count * cl_particles_quality.value);
1443 k = particlepalette[colorbase + (rand()&3)];
1444 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);
1448 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1451 float z, minz, maxz;
1453 if (!cl_particles.integer) return;
1454 if (dir[2] < 0) // falling
1459 minz = z - fabs(dir[2]) * 0.1;
1460 maxz = z + fabs(dir[2]) * 0.1;
1461 minz = bound(mins[2], minz, maxs[2]);
1462 maxz = bound(mins[2], maxz, maxs[2]);
1464 count = (int)(count * cl_particles_quality.value);
1469 if (!cl_particles_rain.integer) break;
1470 count *= 4; // ick, this should be in the mod or maps?
1474 k = particlepalette[colorbase + (rand()&3)];
1475 if (gamemode == GAME_GOODVSBAD2)
1476 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);
1478 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);
1482 if (!cl_particles_snow.integer) break;
1485 k = particlepalette[colorbase + (rand()&3)];
1486 if (gamemode == GAME_GOODVSBAD2)
1487 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);
1489 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);
1491 VectorCopy(p->vel, p->relativedirection);
1495 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1504 void CL_MoveParticles (void)
1507 int i, maxparticle, j, a, content;
1508 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1509 particletype_t *decaltype, *bloodtype;
1513 // LordHavoc: early out condition
1514 if (!cl.num_particles)
1516 cl.free_particle = 0;
1520 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1521 gravity = frametime * sv_gravity.value;
1522 dvel = 1+4*frametime;
1523 decalfade = frametime * 255 / cl_decals_fadetime.value;
1524 decaltype = particletype + pt_decal;
1525 bloodtype = particletype + pt_blood;
1529 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1533 if (cl.free_particle > i)
1534 cl.free_particle = i;
1539 // heavily optimized decal case
1540 if (p->type == decaltype)
1542 // FIXME: this has fairly wacky handling of alpha
1543 if (cl.time > p->time2 + cl_decals_time.value)
1545 p->alpha -= decalfade;
1549 if (cl.free_particle > i)
1550 cl.free_particle = i;
1556 if (cl.entities[p->owner].render.model == p->ownermodel)
1558 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1559 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1564 if (cl.free_particle > i)
1565 cl.free_particle = i;
1573 p->size += p->sizeincrease * frametime;
1574 p->alpha -= p->alphafade * frametime;
1579 if (cl.free_particle > i)
1580 cl.free_particle = i;
1584 if (p->type->orientation != PARTICLE_BEAM)
1586 VectorCopy(p->org, oldorg);
1587 VectorMA(p->org, frametime, p->vel, p->org);
1588 VectorCopy(p->org, org);
1591 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);
1592 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1593 // or if the trace hit something flagged as NOIMPACT
1594 // then remove the particle
1595 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1600 // react if the particle hit something
1601 if (trace.fraction < 1)
1603 VectorCopy(trace.endpos, p->org);
1604 if (p->type == particletype + pt_rain)
1606 // raindrop - splash on solid/water/slime/lava
1608 // convert from a raindrop particle to a rainsplash decal
1609 VectorCopy(trace.plane.normal, p->vel);
1610 VectorAdd(p->org, p->vel, p->org);
1611 p->type = particletype + pt_raindecal;
1612 p->texnum = tex_rainsplash;
1614 p->alphafade = p->alpha / 0.4;
1617 p->liquidfriction = 0;
1620 p->sizeincrease = p->size * 2;
1623 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);
1625 else if (p->type == bloodtype)
1627 // blood - splash on solid
1628 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1633 if (cl_stainmaps.integer)
1634 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)));
1635 if (!cl_decals.integer)
1640 // convert from a blood particle to a blood decal
1641 VectorCopy(trace.plane.normal, p->vel);
1642 VectorAdd(p->org, p->vel, p->org);
1644 p->type = particletype + pt_decal;
1645 p->texnum = tex_blooddecal[rand()&7];
1647 p->ownermodel = cl.entities[hitent].render.model;
1648 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1649 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1650 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1655 p->liquidfriction = 0;
1659 else if (p->bounce < 0)
1661 // bounce -1 means remove on impact
1667 // anything else - bounce off solid
1668 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1669 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1670 if (DotProduct(p->vel, p->vel) < 0.03)
1671 VectorClear(p->vel);
1675 p->vel[2] -= p->gravity * gravity;
1677 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1679 f = 1.0f - min(p->liquidfriction * frametime, 1);
1680 VectorScale(p->vel, f, p->vel);
1682 else if (p->airfriction)
1684 f = 1.0f - min(p->airfriction * frametime, 1);
1685 VectorScale(p->vel, f, p->vel);
1689 if (p->type != particletype + pt_static)
1691 switch (p->type - particletype)
1693 case pt_entityparticle:
1694 // particle that removes itself after one rendered frame
1701 a = CL_PointSuperContents(p->org);
1702 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1704 p->size += frametime * 8;
1705 //p->alpha -= bloodwaterfade;
1708 p->vel[2] -= gravity;
1709 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1713 a = CL_PointSuperContents(p->org);
1714 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1721 a = CL_PointSuperContents(p->org);
1722 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1726 if (cl.time > p->time2)
1729 p->time2 = cl.time + (rand() & 3) * 0.1;
1730 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1731 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1732 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1734 a = CL_PointSuperContents(p->org);
1735 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1743 cl.num_particles = maxparticle + 1;
1746 #define MAX_PARTICLETEXTURES 64
1747 // particletexture_t is a rectangle in the particlefonttexture
1748 typedef struct particletexture_s
1750 rtexture_t *texture;
1751 float s1, t1, s2, t2;
1755 static rtexturepool_t *particletexturepool;
1756 static rtexture_t *particlefonttexture;
1757 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1759 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1761 #define PARTICLETEXTURESIZE 64
1762 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1764 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1768 dz = 1 - (dx*dx+dy*dy);
1769 if (dz > 0) // it does hit the sphere
1773 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1774 VectorNormalize(normal);
1775 dot = DotProduct(normal, light);
1776 if (dot > 0.5) // interior reflection
1777 f += ((dot * 2) - 1);
1778 else if (dot < -0.5) // exterior reflection
1779 f += ((dot * -2) - 1);
1781 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1782 VectorNormalize(normal);
1783 dot = DotProduct(normal, light);
1784 if (dot > 0.5) // interior reflection
1785 f += ((dot * 2) - 1);
1786 else if (dot < -0.5) // exterior reflection
1787 f += ((dot * -2) - 1);
1789 f += 16; // just to give it a haze so you can see the outline
1790 f = bound(0, f, 255);
1791 return (unsigned char) f;
1797 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1799 int basex, basey, y;
1800 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1801 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1802 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1803 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1806 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1809 float cx, cy, dx, dy, f, iradius;
1811 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1812 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1813 iradius = 1.0f / radius;
1814 alpha *= (1.0f / 255.0f);
1815 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1817 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1821 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1824 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1825 d[0] += (int)(f * (red - d[0]));
1826 d[1] += (int)(f * (green - d[1]));
1827 d[2] += (int)(f * (blue - d[2]));
1833 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1836 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1838 data[0] = bound(minr, data[0], maxr);
1839 data[1] = bound(ming, data[1], maxg);
1840 data[2] = bound(minb, data[2], maxb);
1844 void particletextureinvert(unsigned char *data)
1847 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1849 data[0] = 255 - data[0];
1850 data[1] = 255 - data[1];
1851 data[2] = 255 - data[2];
1855 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1856 static void R_InitBloodTextures (unsigned char *particletexturedata)
1859 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1862 for (i = 0;i < 8;i++)
1864 memset(&data[0][0][0], 255, sizeof(data));
1865 for (k = 0;k < 24;k++)
1866 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1867 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1868 particletextureinvert(&data[0][0][0]);
1869 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1873 for (i = 0;i < 8;i++)
1875 memset(&data[0][0][0], 255, sizeof(data));
1877 for (j = 1;j < 10;j++)
1878 for (k = min(j, m - 1);k < m;k++)
1879 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1880 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1881 particletextureinvert(&data[0][0][0]);
1882 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1887 //uncomment this to make engine save out particle font to a tga file when run
1888 //#define DUMPPARTICLEFONT
1890 static void R_InitParticleTexture (void)
1892 int x, y, d, i, k, m;
1896 // a note: decals need to modulate (multiply) the background color to
1897 // properly darken it (stain), and they need to be able to alpha fade,
1898 // this is a very difficult challenge because it means fading to white
1899 // (no change to background) rather than black (darkening everything
1900 // behind the whole decal polygon), and to accomplish this the texture is
1901 // inverted (dark red blood on white background becomes brilliant cyan
1902 // and white on black background) so we can alpha fade it to black, then
1903 // we invert it again during the blendfunc to make it work...
1905 #ifndef DUMPPARTICLEFONT
1906 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1907 if (!particlefonttexture)
1910 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1911 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1912 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1915 for (i = 0;i < 8;i++)
1917 memset(&data[0][0][0], 255, sizeof(data));
1920 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1922 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1923 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1925 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1927 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1928 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1930 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1931 d = (noise2[y][x] - 128) * 3 + 192;
1933 d = (int)(d * (1-(dx*dx+dy*dy)));
1934 d = (d * noise1[y][x]) >> 7;
1935 d = bound(0, d, 255);
1936 data[y][x][3] = (unsigned char) d;
1943 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1947 memset(&data[0][0][0], 255, sizeof(data));
1948 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1950 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1951 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1953 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1954 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1955 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1958 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1961 memset(&data[0][0][0], 255, sizeof(data));
1962 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1964 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1965 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1967 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1968 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1969 d = bound(0, d, 255);
1970 data[y][x][3] = (unsigned char) d;
1973 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1976 memset(&data[0][0][0], 255, sizeof(data));
1977 light[0] = 1;light[1] = 1;light[2] = 1;
1978 VectorNormalize(light);
1979 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1981 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1982 // stretch upper half of bubble by +50% and shrink lower half by -50%
1983 // (this gives an elongated teardrop shape)
1985 dy = (dy - 0.5f) * 2.0f;
1987 dy = (dy - 0.5f) / 1.5f;
1988 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1990 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1991 // shrink bubble width to half
1993 data[y][x][3] = shadebubble(dx, dy, light);
1996 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1999 memset(&data[0][0][0], 255, sizeof(data));
2000 light[0] = 1;light[1] = 1;light[2] = 1;
2001 VectorNormalize(light);
2002 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2004 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2005 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2007 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2008 data[y][x][3] = shadebubble(dx, dy, light);
2011 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2013 // Blood particles and blood decals
2014 R_InitBloodTextures (particletexturedata);
2017 for (i = 0;i < 8;i++)
2019 memset(&data[0][0][0], 255, sizeof(data));
2020 for (k = 0;k < 12;k++)
2021 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2022 for (k = 0;k < 3;k++)
2023 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2024 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2025 particletextureinvert(&data[0][0][0]);
2026 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2029 #ifdef DUMPPARTICLEFONT
2030 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2033 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
2035 Mem_Free(particletexturedata);
2037 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2039 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
2040 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
2041 particletexture[i].texture = particlefonttexture;
2042 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
2043 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
2044 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2045 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
2048 #ifndef DUMPPARTICLEFONT
2049 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
2050 if (!particletexture[tex_beam].texture)
2053 unsigned char noise3[64][64], data2[64][16][4];
2055 fractalnoise(&noise3[0][0], 64, 4);
2057 for (y = 0;y < 64;y++)
2059 dy = (y - 0.5f*64) / (64*0.5f-1);
2060 for (x = 0;x < 16;x++)
2062 dx = (x - 0.5f*16) / (16*0.5f-2);
2063 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2064 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2065 data2[y][x][3] = 255;
2069 #ifdef DUMPPARTICLEFONT
2070 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2072 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2074 particletexture[tex_beam].s1 = 0;
2075 particletexture[tex_beam].t1 = 0;
2076 particletexture[tex_beam].s2 = 1;
2077 particletexture[tex_beam].t2 = 1;
2080 static void r_part_start(void)
2082 particletexturepool = R_AllocTexturePool();
2083 R_InitParticleTexture ();
2084 CL_Particles_LoadEffectInfo();
2087 static void r_part_shutdown(void)
2089 R_FreeTexturePool(&particletexturepool);
2092 static void r_part_newmap(void)
2096 #define BATCHSIZE 256
2097 int particle_element3i[BATCHSIZE*6];
2098 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2100 void R_Particles_Init (void)
2103 for (i = 0;i < BATCHSIZE;i++)
2105 particle_element3i[i*6+0] = i*4+0;
2106 particle_element3i[i*6+1] = i*4+1;
2107 particle_element3i[i*6+2] = i*4+2;
2108 particle_element3i[i*6+3] = i*4+0;
2109 particle_element3i[i*6+4] = i*4+2;
2110 particle_element3i[i*6+5] = i*4+3;
2113 Cvar_RegisterVariable(&r_drawparticles);
2114 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2117 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2119 int surfacelistindex;
2120 int batchstart, batchcount;
2121 const particle_t *p;
2123 rtexture_t *texture;
2124 float *v3f, *t2f, *c4f;
2126 R_Mesh_Matrix(&identitymatrix);
2127 R_Mesh_ResetTextureState();
2128 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2129 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2130 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2131 GL_DepthMask(false);
2132 GL_DepthRange(0, 1);
2134 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2136 // first generate all the vertices at once
2137 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2139 particletexture_t *tex;
2141 float up2[3], v[3], right[3], up[3], fog, cr, cg, cb, ca, size;
2143 p = cl.particles + surfacelist[surfacelistindex];
2145 blendmode = p->type->blendmode;
2147 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2148 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2149 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2150 ca = p->alpha * (1.0f / 255.0f);
2151 if (blendmode == PBLEND_MOD)
2161 ca /= cl_particles_quality.value;
2162 if (p->type->lighting)
2164 float ambient[3], diffuse[3], diffusenormal[3];
2165 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2166 cr *= (ambient[0] + 0.5 * diffuse[0]);
2167 cg *= (ambient[1] + 0.5 * diffuse[1]);
2168 cb *= (ambient[2] + 0.5 * diffuse[2]);
2170 if (r_refdef.fogenabled)
2172 fog = FogPoint_World(p->org);
2176 if (blendmode == PBLEND_ALPHA)
2179 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2180 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2181 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2184 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2185 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2186 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2187 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2189 size = p->size * cl_particles_size.value;
2191 tex = &particletexture[p->texnum];
2192 if (p->type->orientation == PARTICLE_BILLBOARD)
2194 VectorScale(r_view.left, -size, right);
2195 VectorScale(r_view.up, size, up);
2196 v3f[ 0] = org[0] - right[0] - up[0];
2197 v3f[ 1] = org[1] - right[1] - up[1];
2198 v3f[ 2] = org[2] - right[2] - up[2];
2199 v3f[ 3] = org[0] - right[0] + up[0];
2200 v3f[ 4] = org[1] - right[1] + up[1];
2201 v3f[ 5] = org[2] - right[2] + up[2];
2202 v3f[ 6] = org[0] + right[0] + up[0];
2203 v3f[ 7] = org[1] + right[1] + up[1];
2204 v3f[ 8] = org[2] + right[2] + up[2];
2205 v3f[ 9] = org[0] + right[0] - up[0];
2206 v3f[10] = org[1] + right[1] - up[1];
2207 v3f[11] = org[2] + right[2] - up[2];
2208 t2f[0] = tex->s1;t2f[1] = tex->t2;
2209 t2f[2] = tex->s1;t2f[3] = tex->t1;
2210 t2f[4] = tex->s2;t2f[5] = tex->t1;
2211 t2f[6] = tex->s2;t2f[7] = tex->t2;
2213 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2216 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2218 VectorNegate(p->vel, v);
2219 VectorVectors(v, right, up);
2222 VectorVectors(p->vel, right, up);
2223 VectorScale(right, size, right);
2224 VectorScale(up, size, up);
2225 v3f[ 0] = org[0] - right[0] - up[0];
2226 v3f[ 1] = org[1] - right[1] - up[1];
2227 v3f[ 2] = org[2] - right[2] - up[2];
2228 v3f[ 3] = org[0] - right[0] + up[0];
2229 v3f[ 4] = org[1] - right[1] + up[1];
2230 v3f[ 5] = org[2] - right[2] + up[2];
2231 v3f[ 6] = org[0] + right[0] + up[0];
2232 v3f[ 7] = org[1] + right[1] + up[1];
2233 v3f[ 8] = org[2] + right[2] + up[2];
2234 v3f[ 9] = org[0] + right[0] - up[0];
2235 v3f[10] = org[1] + right[1] - up[1];
2236 v3f[11] = org[2] + right[2] - up[2];
2237 t2f[0] = tex->s1;t2f[1] = tex->t2;
2238 t2f[2] = tex->s1;t2f[3] = tex->t1;
2239 t2f[4] = tex->s2;t2f[5] = tex->t1;
2240 t2f[6] = tex->s2;t2f[7] = tex->t2;
2242 else if (p->type->orientation == PARTICLE_SPARK)
2244 VectorMA(org, -0.02, p->vel, v);
2245 VectorMA(org, 0.02, p->vel, up2);
2246 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2247 t2f[0] = tex->s1;t2f[1] = tex->t2;
2248 t2f[2] = tex->s1;t2f[3] = tex->t1;
2249 t2f[4] = tex->s2;t2f[5] = tex->t1;
2250 t2f[6] = tex->s2;t2f[7] = tex->t2;
2252 else if (p->type->orientation == PARTICLE_BEAM)
2254 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2255 VectorSubtract(p->vel, org, up);
2256 VectorNormalize(up);
2257 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2258 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2259 t2f[0] = 1;t2f[1] = v[0];
2260 t2f[2] = 0;t2f[3] = v[0];
2261 t2f[4] = 0;t2f[5] = v[1];
2262 t2f[6] = 1;t2f[7] = v[1];
2266 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2271 // now render batches of particles based on blendmode and texture
2272 blendmode = PBLEND_ADD;
2273 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2274 texture = particletexture[63].texture;
2275 R_Mesh_TexBind(0, R_GetTexture(texture));
2276 GL_LockArrays(0, numsurfaces*4);
2279 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2281 p = cl.particles + surfacelist[surfacelistindex];
2283 if (blendmode != p->type->blendmode)
2286 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2288 batchstart = surfacelistindex;
2289 blendmode = p->type->blendmode;
2290 if (blendmode == PBLEND_ALPHA)
2291 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2292 else if (blendmode == PBLEND_ADD)
2293 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2294 else //if (blendmode == PBLEND_MOD)
2295 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2297 if (texture != particletexture[p->texnum].texture)
2300 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2302 batchstart = surfacelistindex;
2303 texture = particletexture[p->texnum].texture;
2304 R_Mesh_TexBind(0, R_GetTexture(texture));
2310 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6, 0, 0);
2311 GL_LockArrays(0, 0);
2314 void R_DrawParticles (void)
2317 float minparticledist;
2320 // LordHavoc: early out conditions
2321 if ((!cl.num_particles) || (!r_drawparticles.integer))
2324 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2326 // LordHavoc: only render if not too close
2327 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2331 r_refdef.stats.particles++;
2332 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2333 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);