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_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
183 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
184 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
185 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
186 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
187 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"};
188 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "0", "how long before decals start to fade away"};
189 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "20", "how long decals take to fade away"};
192 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
198 particleeffectinfo_t *info = NULL;
199 const char *text = textstart;
201 effectinfoindex = -1;
202 for (linenumber = 1;;linenumber++)
205 for (arrayindex = 0;arrayindex < 16;arrayindex++)
206 argv[arrayindex][0] = 0;
209 if (!COM_ParseToken(&text, true))
211 if (!strcmp(com_token, "\n"))
215 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
221 #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;}
222 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
223 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
224 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
225 #define readfloat(var) checkparms(2);var = atof(argv[1])
226 if (!strcmp(argv[0], "effect"))
231 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
233 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
236 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
238 if (particleeffectname[effectnameindex][0])
240 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
245 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
249 // if we run out of names, abort
250 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
252 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
255 info = particleeffectinfo + effectinfoindex;
256 info->effectnameindex = effectnameindex;
257 info->particletype = pt_alphastatic;
258 info->tex[0] = tex_particle;
259 info->tex[1] = tex_particle;
260 info->color[0] = 0xFFFFFF;
261 info->color[1] = 0xFFFFFF;
265 info->alpha[1] = 256;
266 info->alpha[2] = 256;
267 info->time[0] = 9999;
268 info->time[1] = 9999;
269 VectorSet(info->lightcolor, 1, 1, 1);
270 info->lightshadow = true;
271 info->lighttime = 9999;
273 else if (info == NULL)
275 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
278 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
279 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
280 else if (!strcmp(argv[0], "type"))
283 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
284 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
285 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
286 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
287 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
288 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
289 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
290 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
291 else if (!strcmp(argv[1], "blood")) info->particletype = pt_blood;
292 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
293 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
294 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
295 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
297 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
298 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
299 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
300 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
301 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
302 else if (!strcmp(argv[0], "time")) {readints(info->time, 2);}
303 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
304 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
305 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
306 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
307 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
308 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
309 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
310 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
311 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
312 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
313 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
314 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
315 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
316 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
317 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
318 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
319 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
320 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
322 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
331 int CL_ParticleEffectIndexForName(const char *name)
334 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
335 if (!strcmp(particleeffectname[i], name))
340 const char *CL_ParticleEffectNameForIndex(int i)
342 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
344 return particleeffectname[i];
347 // MUST match effectnameindex_t in client.h
348 static const char *standardeffectnames[EFFECT_TOTAL] =
372 "TE_TEI_BIGEXPLOSION",
388 void CL_Particles_LoadEffectInfo(void)
391 unsigned char *filedata;
392 fs_offset_t filesize;
393 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
394 memset(particleeffectname, 0, sizeof(particleeffectname));
395 for (i = 0;i < EFFECT_TOTAL;i++)
396 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
397 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
400 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
410 void CL_ReadPointFile_f (void);
411 void CL_Particles_Init (void)
413 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)");
414 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
416 Cvar_RegisterVariable (&cl_particles);
417 Cvar_RegisterVariable (&cl_particles_quality);
418 Cvar_RegisterVariable (&cl_particles_size);
419 Cvar_RegisterVariable (&cl_particles_quake);
420 Cvar_RegisterVariable (&cl_particles_blood);
421 Cvar_RegisterVariable (&cl_particles_blood_alpha);
422 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
423 Cvar_RegisterVariable (&cl_particles_explosions_smoke);
424 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
425 Cvar_RegisterVariable (&cl_particles_explosions_shell);
426 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
427 Cvar_RegisterVariable (&cl_particles_smoke);
428 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
429 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
430 Cvar_RegisterVariable (&cl_particles_sparks);
431 Cvar_RegisterVariable (&cl_particles_bubbles);
432 Cvar_RegisterVariable (&cl_decals);
433 Cvar_RegisterVariable (&cl_decals_time);
434 Cvar_RegisterVariable (&cl_decals_fadetime);
437 void CL_Particles_Shutdown (void)
441 // list of all 26 parameters:
442 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
443 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
444 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
445 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
446 // palpha - opacity of particle as 0-255 (can be more than 255)
447 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
448 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
449 // pgravity - how much effect gravity has on the particle (0-1)
450 // 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
451 // px,py,pz - starting origin of particle
452 // pvx,pvy,pvz - starting velocity of particle
453 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
454 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)
459 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].type;cl.free_particle++);
460 if (cl.free_particle >= cl.max_particles)
462 part = &cl.particles[cl.free_particle++];
463 if (cl.num_particles < cl.free_particle)
464 cl.num_particles = cl.free_particle;
465 memset(part, 0, sizeof(*part));
467 l2 = (int)lhrandom(0.5, 256.5);
469 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
470 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
471 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
472 part->color[3] = 0xFF;
475 part->sizeincrease = psizeincrease;
476 part->alpha = palpha;
477 part->alphafade = palphafade;
478 part->gravity = pgravity;
479 part->bounce = pbounce;
481 part->org[0] = px + originjitter * v[0];
482 part->org[1] = py + originjitter * v[1];
483 part->org[2] = pz + originjitter * v[2];
484 part->vel[0] = pvx + velocityjitter * v[0];
485 part->vel[1] = pvy + velocityjitter * v[1];
486 part->vel[2] = pvz + velocityjitter * v[2];
488 part->airfriction = pairfriction;
489 part->liquidfriction = pliquidfriction;
493 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
496 if (!cl_decals.integer)
498 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);
503 p->ownermodel = cl.entities[p->owner].render.model;
504 VectorAdd(org, normal, p->org);
505 VectorCopy(normal, p->vel);
506 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
507 Matrix4x4_Transform(&cl.entities[p->owner].render.inversematrix, org, p->relativeorigin);
508 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.inversematrix, normal, p->relativedirection);
512 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
515 float bestfrac, bestorg[3], bestnormal[3];
517 int besthitent = 0, hitent;
520 for (i = 0;i < 32;i++)
523 VectorMA(org, maxdist, org2, org2);
524 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
525 // take the closest trace result that doesn't end up hitting a NOMARKS
526 // surface (sky for example)
527 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
529 bestfrac = trace.fraction;
531 VectorCopy(trace.endpos, bestorg);
532 VectorCopy(trace.plane.normal, bestnormal);
536 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
539 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount);
540 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)
543 matrix4x4_t tempmatrix;
544 VectorLerp(originmins, 0.5, originmaxs, center);
545 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
546 if (effectnameindex == EFFECT_SVC_PARTICLE)
548 if (cl_particles.integer)
550 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
552 CL_ParticleExplosion(center);
553 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
554 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 6.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
557 count *= cl_particles_quality.value;
558 for (;count > 0;count--)
560 int k = particlepalette[palettecolor + (rand()&7)];
561 if (cl_particles_quake.integer)
562 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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);
563 else if (gamemode == GAME_GOODVSBAD2)
564 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);
566 particle(particletype + pt_alphastatic, k, k, tex_particle, 1, 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);
571 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
572 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
573 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
574 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
575 else if (effectnameindex == EFFECT_TE_SPIKE)
577 if (cl_particles_bulletimpacts.integer)
579 if (cl_particles_quake.integer)
581 if (cl_particles_smoke.integer)
582 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
585 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
588 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
589 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
591 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
593 if (cl_particles_bulletimpacts.integer)
595 if (cl_particles_quake.integer)
597 if (cl_particles_smoke.integer)
598 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
601 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
604 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
605 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
606 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);
608 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
610 if (cl_particles_bulletimpacts.integer)
612 if (cl_particles_quake.integer)
614 if (cl_particles_smoke.integer)
615 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
618 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
621 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
622 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
624 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
626 if (cl_particles_bulletimpacts.integer)
628 if (cl_particles_quake.integer)
630 if (cl_particles_smoke.integer)
631 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
634 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count, 8*count);
637 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
638 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
639 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);
641 else if (effectnameindex == EFFECT_TE_BLOOD)
643 if (!cl_particles_blood.integer)
645 if (cl_particles_quake.integer)
646 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
649 static double bloodaccumulator = 0;
650 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
651 for (;bloodaccumulator > 0;bloodaccumulator--)
652 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);
655 else if (effectnameindex == EFFECT_TE_SPARK)
656 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count, 0);
657 else if (effectnameindex == EFFECT_TE_PLASMABURN)
659 // plasma scorch mark
660 if (cl_stainmaps.integer) R_Stain(center, 48, 96, 96, 96, 32, 128, 128, 128, 32);
661 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
662 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
664 else if (effectnameindex == EFFECT_TE_GUNSHOT)
666 if (cl_particles_bulletimpacts.integer)
668 if (cl_particles_quake.integer)
669 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
671 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
674 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
675 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
677 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
679 if (cl_particles_bulletimpacts.integer)
681 if (cl_particles_quake.integer)
682 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
684 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count, 4*count);
687 if (cl_stainmaps.integer) R_Stain(center, 32, 96, 96, 96, 24, 128, 128, 128, 24);
688 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
689 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);
691 else if (effectnameindex == EFFECT_TE_EXPLOSION)
693 CL_ParticleExplosion(center);
694 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);
696 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
698 CL_ParticleExplosion(center);
699 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);
701 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
703 if (cl_particles_quake.integer)
706 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
709 particle(particletype + pt_static, particlepalette[66], particlepalette[71], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256);
711 particle(particletype + pt_static, particlepalette[150], particlepalette[155], tex_particle, 1, 0, lhrandom(182, 255), 182, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0);
715 CL_ParticleExplosion(center);
716 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);
718 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
719 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);
720 else if (effectnameindex == EFFECT_TE_FLAMEJET)
722 count *= cl_particles_quality.value;
724 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);
726 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
728 float i, j, inc, vel;
731 inc = 8 / cl_particles_quality.value;
732 for (i = -128;i < 128;i += inc)
734 for (j = -128;j < 128;j += inc)
736 dir[0] = j + lhrandom(0, inc);
737 dir[1] = i + lhrandom(0, inc);
739 org[0] = center[0] + dir[0];
740 org[1] = center[1] + dir[1];
741 org[2] = center[2] + lhrandom(0, 64);
742 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
743 particle(particletype + pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1, 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);
747 else if (effectnameindex == EFFECT_TE_TELEPORT)
749 float i, j, k, inc, vel;
752 inc = 8 / cl_particles_quality.value;
753 for (i = -16;i < 16;i += inc)
755 for (j = -16;j < 16;j += inc)
757 for (k = -24;k < 32;k += inc)
759 VectorSet(dir, i*8, j*8, k*8);
760 VectorNormalize(dir);
761 vel = lhrandom(50, 113);
762 particle(particletype + pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1, 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);
766 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);
767 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);
769 else if (effectnameindex == EFFECT_TE_TEI_G3)
770 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);
771 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
773 if (cl_particles_smoke.integer)
775 count *= 0.25f * cl_particles_quality.value;
777 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);
780 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
782 CL_ParticleExplosion(center);
783 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);
785 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
788 if (cl_stainmaps.integer)
789 R_Stain(center, 40, 96, 96, 96, 40, 128, 128, 128, 40);
790 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
791 if (cl_particles_smoke.integer)
792 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
793 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);
794 if (cl_particles_sparks.integer)
795 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
796 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);
797 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);
799 else if (effectnameindex == EFFECT_EF_FLAME)
801 count *= 300 * cl_particles_quality.value;
803 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);
804 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);
806 else if (effectnameindex == EFFECT_EF_STARDUST)
808 count *= 200 * cl_particles_quality.value;
810 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);
811 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);
813 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
817 int smoke, blood, bubbles, r, color;
819 if (spawndlight && r_refdef.numlights < MAX_DLIGHTS)
822 Vector4Set(light, 0, 0, 0, 0);
824 if (effectnameindex == EFFECT_TR_ROCKET)
825 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
826 else if (effectnameindex == EFFECT_TR_VORESPIKE)
828 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
829 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
831 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
833 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
834 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
838 matrix4x4_t tempmatrix;
839 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
840 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);
847 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
850 VectorSubtract(originmaxs, originmins, dir);
851 len = VectorNormalizeLength(dir);
852 dec = -ent->persistent.trail_time;
853 ent->persistent.trail_time += len;
854 if (ent->persistent.trail_time < 0.01f)
857 // if we skip out, leave it reset
858 ent->persistent.trail_time = 0.0f;
860 // advance into this frame to reach the first puff location
861 VectorMA(originmins, dec, dir, pos);
864 smoke = cl_particles.integer && cl_particles_smoke.integer;
865 blood = cl_particles.integer && cl_particles_blood.integer;
866 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
867 qd = 1.0f / cl_particles_quality.value;
874 if (effectnameindex == EFFECT_TR_BLOOD)
876 if (cl_particles_quake.integer)
878 color = particlepalette[67 + (rand()&3)];
879 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
884 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);
887 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
889 if (cl_particles_quake.integer)
892 color = particlepalette[67 + (rand()&3)];
893 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 128, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
898 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);
904 if (effectnameindex == EFFECT_TR_ROCKET)
906 if (cl_particles_quake.integer)
909 color = particlepalette[ramp3[r]];
910 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
914 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);
915 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);
918 else if (effectnameindex == EFFECT_TR_GRENADE)
920 if (cl_particles_quake.integer)
923 color = particlepalette[ramp3[r]];
924 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 42*(6-r), 306, 0, -0.05, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0);
928 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);
931 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
933 if (cl_particles_quake.integer)
936 color = particlepalette[52 + (rand()&7)];
937 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
938 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
940 else if (gamemode == GAME_GOODVSBAD2)
943 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);
947 color = particlepalette[20 + (rand()&7)];
948 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);
951 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
953 if (cl_particles_quake.integer)
956 color = particlepalette[230 + (rand()&7)];
957 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0);
958 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 512, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0);
962 color = particlepalette[226 + (rand()&7)];
963 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);
966 else if (effectnameindex == EFFECT_TR_VORESPIKE)
968 if (cl_particles_quake.integer)
970 color = particlepalette[152 + (rand()&3)];
971 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 255, 850, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0);
973 else if (gamemode == GAME_GOODVSBAD2)
976 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);
978 else if (gamemode == GAME_PRYDON)
981 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);
984 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);
986 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
989 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);
991 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
994 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);
996 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
997 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);
1001 if (effectnameindex == EFFECT_TR_ROCKET)
1002 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);
1003 else if (effectnameindex == EFFECT_TR_GRENADE)
1004 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);
1006 // advance to next time and position
1009 VectorMA (pos, dec, dir, pos);
1011 ent->persistent.trail_time = len;
1013 else if (developer.integer >= 1)
1014 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1017 // this is also called on point effects with spawndlight = true and
1018 // spawnparticles = true
1019 // it is called CL_ParticleTrail because most code does not want to supply
1020 // these parameters, only trail handling does
1021 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)
1024 qboolean found = false;
1025 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME)
1026 return; // invalid effect index
1027 if (!particleeffectname[effectnameindex][0])
1028 return; // no such effect
1029 VectorLerp(originmins, 0.5, originmaxs, center);
1030 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1032 int effectinfoindex;
1035 particleeffectinfo_t *info;
1037 vec3_t centervelocity;
1043 qboolean underwater;
1044 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1045 VectorLerp(originmins, 0.5, originmaxs, center);
1046 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1047 supercontents = CL_PointSuperContents(center);
1048 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1049 VectorSubtract(originmaxs, originmins, traildir);
1050 traillen = VectorLength(traildir);
1051 VectorNormalize(traildir);
1052 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1054 if (info->effectnameindex == effectnameindex)
1057 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1059 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1062 // spawn a dlight if requested
1063 if (info->lightradiusstart > 0 && spawndlight)
1065 matrix4x4_t tempmatrix;
1066 if (info->trailspacing > 0)
1067 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1069 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1070 if (info->lighttime > 0 && info->lightradiusfade > 0)
1072 // light flash (explosion, etc)
1073 // called when effect starts
1074 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);
1079 // called by CL_LinkNetworkEntity
1080 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1081 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);
1085 if (!spawnparticles)
1090 if (info->tex[1] > info->tex[0])
1092 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1093 tex = min(tex, info->tex[1] - 1);
1095 if (info->particletype == pt_decal)
1096 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]);
1097 else if (info->particletype == pt_beam)
1098 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);
1101 if (!cl_particles.integer)
1103 switch (info->particletype)
1105 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1106 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1107 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1108 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1111 VectorCopy(originmins, trailpos);
1112 if (info->trailspacing > 0)
1114 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1115 trailstep = info->trailspacing / cl_particles_quality.value;
1119 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1122 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1123 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1125 if (info->tex[1] > info->tex[0])
1127 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1128 tex = min(tex, info->tex[1] - 1);
1132 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1133 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1134 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1137 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);
1139 VectorMA(trailpos, trailstep, traildir, trailpos);
1146 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1149 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)
1151 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1159 void CL_EntityParticles (const entity_t *ent)
1162 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1163 static vec3_t avelocities[NUMVERTEXNORMALS];
1164 if (!cl_particles.integer) return;
1166 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1168 if (!avelocities[0][0])
1169 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1170 avelocities[0][i] = lhrandom(0, 2.55);
1172 for (i = 0;i < NUMVERTEXNORMALS;i++)
1174 yaw = cl.time * avelocities[i][0];
1175 pitch = cl.time * avelocities[i][1];
1176 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1177 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1178 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1179 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);
1184 void CL_ReadPointFile_f (void)
1186 vec3_t org, leakorg;
1188 char *pointfile = NULL, *pointfilepos, *t, tchar;
1189 char name[MAX_OSPATH];
1194 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1195 strlcat (name, ".pts", sizeof (name));
1196 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1199 Con_Printf("Could not open %s\n", name);
1203 Con_Printf("Reading %s...\n", name);
1204 VectorClear(leakorg);
1207 pointfilepos = pointfile;
1208 while (*pointfilepos)
1210 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1215 while (*t && *t != '\n' && *t != '\r')
1219 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1225 VectorCopy(org, leakorg);
1228 if (cl.num_particles < cl.max_particles - 3)
1231 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);
1234 Mem_Free(pointfile);
1235 VectorCopy(leakorg, org);
1236 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1238 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);
1239 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);
1240 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);
1245 CL_ParseParticleEffect
1247 Parse an effect out of the server message
1250 void CL_ParseParticleEffect (void)
1253 int i, count, msgcount, color;
1255 MSG_ReadVector(org, cls.protocol);
1256 for (i=0 ; i<3 ; i++)
1257 dir[i] = MSG_ReadChar ();
1258 msgcount = MSG_ReadByte ();
1259 color = MSG_ReadByte ();
1261 if (msgcount == 255)
1266 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1271 CL_ParticleExplosion
1275 void CL_ParticleExplosion (const vec3_t org)
1281 if (cl_stainmaps.integer)
1282 R_Stain(org, 96, 80, 80, 80, 64, 176, 176, 176, 64);
1283 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1285 if (cl_particles_quake.integer)
1287 for (i = 0;i < 1024;i++)
1293 color = particlepalette[ramp1[r]];
1294 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 318, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256);
1298 color = particlepalette[ramp2[r]];
1299 particle(particletype + pt_alphastatic, color, color, tex_particle, 1, 0, 32 * (8 - r), 478, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256);
1305 i = CL_PointSuperContents(org);
1306 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1308 if (cl_particles.integer && cl_particles_bubbles.integer)
1309 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1310 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);
1314 // LordHavoc: smoke effect similar to UT2003, chews fillrate too badly up close
1316 if (cl_particles.integer && cl_particles_smoke.integer && cl_particles_explosions_smoke.integer)
1318 for (i = 0;i < 32;i++)
1322 for (k = 0;k < 16;k++)
1324 v[0] = org[0] + lhrandom(-48, 48);
1325 v[1] = org[1] + lhrandom(-48, 48);
1326 v[2] = org[2] + lhrandom(-48, 48);
1327 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1328 if (trace.fraction >= 0.1)
1331 VectorSubtract(trace.endpos, org, v2);
1332 VectorScale(v2, 2.0f, v2);
1333 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);
1337 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1338 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1339 particle(particletype + pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 1, 0, org[0], org[1], org[2], 0, 0, 80, 0.2, 0.8, 0, 256);
1343 if (cl_particles_explosions_shell.integer)
1344 R_NewExplosion(org);
1349 CL_ParticleExplosion2
1353 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1356 if (!cl_particles.integer) return;
1358 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1360 k = particlepalette[colorStart + (i % colorLength)];
1361 if (cl_particles_quake.integer)
1362 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);
1364 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);
1368 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount, float smokecount)
1370 if (cl_particles_sparks.integer)
1372 sparkcount *= cl_particles_quality.value;
1373 while(sparkcount-- > 0)
1374 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);
1376 if (cl_particles_smoke.integer)
1378 smokecount *= cl_particles_quality.value;
1379 while(smokecount-- > 0)
1380 particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, 0, 255, 1024, 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, 8);
1384 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)
1387 if (!cl_particles.integer) return;
1389 count = (int)(count * cl_particles_quality.value);
1392 k = particlepalette[colorbase + (rand()&3)];
1393 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);
1397 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1400 float z, minz, maxz;
1402 if (!cl_particles.integer) return;
1403 if (dir[2] < 0) // falling
1408 minz = z - fabs(dir[2]) * 0.1;
1409 maxz = z + fabs(dir[2]) * 0.1;
1410 minz = bound(mins[2], minz, maxs[2]);
1411 maxz = bound(mins[2], maxz, maxs[2]);
1413 count = (int)(count * cl_particles_quality.value);
1418 count *= 4; // ick, this should be in the mod or maps?
1422 k = particlepalette[colorbase + (rand()&3)];
1423 if (gamemode == GAME_GOODVSBAD2)
1424 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);
1426 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);
1432 k = particlepalette[colorbase + (rand()&3)];
1433 if (gamemode == GAME_GOODVSBAD2)
1434 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);
1436 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);
1438 VectorCopy(p->vel, p->relativedirection);
1442 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1451 void CL_MoveParticles (void)
1454 int i, maxparticle, j, a, content;
1455 float gravity, dvel, decalfade, frametime, f, dist, org[3], oldorg[3];
1456 particletype_t *decaltype, *bloodtype;
1460 // LordHavoc: early out condition
1461 if (!cl.num_particles)
1463 cl.free_particle = 0;
1467 frametime = bound(0, cl.time - cl.oldtime, 0.1);
1468 gravity = frametime * sv_gravity.value;
1469 dvel = 1+4*frametime;
1470 decalfade = frametime * 255 / cl_decals_fadetime.value;
1471 decaltype = particletype + pt_decal;
1472 bloodtype = particletype + pt_blood;
1476 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
1480 if (cl.free_particle > i)
1481 cl.free_particle = i;
1486 // heavily optimized decal case
1487 if (p->type == decaltype)
1489 // FIXME: this has fairly wacky handling of alpha
1490 if (cl.time > p->time2 + cl_decals_time.value)
1492 p->alpha -= decalfade;
1496 if (cl.free_particle > i)
1497 cl.free_particle = i;
1503 if (cl.entities[p->owner].render.model == p->ownermodel)
1505 Matrix4x4_Transform(&cl.entities[p->owner].render.matrix, p->relativeorigin, p->org);
1506 Matrix4x4_Transform3x3(&cl.entities[p->owner].render.matrix, p->relativedirection, p->vel);
1511 if (cl.free_particle > i)
1512 cl.free_particle = i;
1520 p->alpha -= p->alphafade * frametime;
1525 if (cl.free_particle > i)
1526 cl.free_particle = i;
1530 if (p->type->orientation != PARTICLE_BEAM)
1532 VectorCopy(p->org, oldorg);
1533 VectorMA(p->org, frametime, p->vel, p->org);
1534 VectorCopy(p->org, org);
1537 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);
1538 // if the trace started in or hit something of SUPERCONTENTS_NODROP
1539 // or if the trace hit something flagged as NOIMPACT
1540 // then remove the particle
1541 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP))
1546 // react if the particle hit something
1547 if (trace.fraction < 1)
1549 VectorCopy(trace.endpos, p->org);
1550 if (p->type == particletype + pt_rain)
1552 // raindrop - splash on solid/water/slime/lava
1554 // convert from a raindrop particle to a rainsplash decal
1555 VectorCopy(trace.plane.normal, p->vel);
1556 VectorAdd(p->org, p->vel, p->org);
1557 p->type = particletype + pt_raindecal;
1558 p->texnum = tex_rainsplash;
1560 p->alphafade = p->alpha / 0.4;
1563 p->liquidfriction = 0;
1566 p->sizeincrease = p->size * 16;
1569 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, 32 + p->vel[2]*16, 0, 0, 0, 32);
1571 else if (p->type == bloodtype)
1573 // blood - splash on solid
1574 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
1579 if (!cl_decals.integer)
1584 // convert from a blood particle to a blood decal
1585 VectorCopy(trace.plane.normal, p->vel);
1586 VectorAdd(p->org, p->vel, p->org);
1587 if (cl_stainmaps.integer)
1588 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)));
1590 p->type = particletype + pt_decal;
1591 p->texnum = tex_blooddecal[rand()&7];
1593 p->ownermodel = cl.entities[hitent].render.model;
1594 // these relative things are only used to regenerate p->org and p->vel if p->owner is not world (0)
1595 Matrix4x4_Transform(&cl.entities[hitent].render.inversematrix, p->org, p->relativeorigin);
1596 Matrix4x4_Transform3x3(&cl.entities[hitent].render.inversematrix, p->vel, p->relativedirection);
1601 p->liquidfriction = 0;
1605 else if (p->bounce < 0)
1607 // bounce -1 means remove on impact
1613 // anything else - bounce off solid
1614 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
1615 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
1616 if (DotProduct(p->vel, p->vel) < 0.03)
1617 VectorClear(p->vel);
1621 p->vel[2] -= p->gravity * gravity;
1623 if (p->liquidfriction && CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)
1625 f = 1.0f - min(p->liquidfriction * frametime, 1);
1626 VectorScale(p->vel, f, p->vel);
1628 else if (p->airfriction)
1630 f = 1.0f - min(p->airfriction * frametime, 1);
1631 VectorScale(p->vel, f, p->vel);
1635 if (p->type != particletype + pt_static)
1637 switch (p->type - particletype)
1639 case pt_entityparticle:
1640 // particle that removes itself after one rendered frame
1647 a = CL_PointSuperContents(p->org);
1648 if (a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))
1650 p->size += frametime * 8;
1651 //p->alpha -= bloodwaterfade;
1654 p->vel[2] -= gravity;
1655 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
1659 a = CL_PointSuperContents(p->org);
1660 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
1667 a = CL_PointSuperContents(p->org);
1668 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1672 if (cl.time > p->time2)
1675 p->time2 = cl.time + (rand() & 3) * 0.1;
1676 p->vel[0] = p->relativedirection[0] + lhrandom(-32, 32);
1677 p->vel[1] = p->relativedirection[1] + lhrandom(-32, 32);
1678 //p->vel[2] = p->relativedirection[2] + lhrandom(-32, 32);
1680 a = CL_PointSuperContents(p->org);
1681 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
1689 cl.num_particles = maxparticle + 1;
1692 #define MAX_PARTICLETEXTURES 64
1693 // particletexture_t is a rectangle in the particlefonttexture
1694 typedef struct particletexture_s
1696 rtexture_t *texture;
1697 float s1, t1, s2, t2;
1701 static rtexturepool_t *particletexturepool;
1702 static rtexture_t *particlefonttexture;
1703 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1705 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1707 #define PARTICLETEXTURESIZE 64
1708 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1710 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1714 dz = 1 - (dx*dx+dy*dy);
1715 if (dz > 0) // it does hit the sphere
1719 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1720 VectorNormalize(normal);
1721 dot = DotProduct(normal, light);
1722 if (dot > 0.5) // interior reflection
1723 f += ((dot * 2) - 1);
1724 else if (dot < -0.5) // exterior reflection
1725 f += ((dot * -2) - 1);
1727 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1728 VectorNormalize(normal);
1729 dot = DotProduct(normal, light);
1730 if (dot > 0.5) // interior reflection
1731 f += ((dot * 2) - 1);
1732 else if (dot < -0.5) // exterior reflection
1733 f += ((dot * -2) - 1);
1735 f += 16; // just to give it a haze so you can see the outline
1736 f = bound(0, f, 255);
1737 return (unsigned char) f;
1743 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1745 int basex, basey, y;
1746 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1747 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1748 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1749 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1752 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1755 float cx, cy, dx, dy, f, iradius;
1757 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1758 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1759 iradius = 1.0f / radius;
1760 alpha *= (1.0f / 255.0f);
1761 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1763 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1767 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1770 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1771 d[0] += (int)(f * (red - d[0]));
1772 d[1] += (int)(f * (green - d[1]));
1773 d[2] += (int)(f * (blue - d[2]));
1779 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1782 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1784 data[0] = bound(minr, data[0], maxr);
1785 data[1] = bound(ming, data[1], maxg);
1786 data[2] = bound(minb, data[2], maxb);
1790 void particletextureinvert(unsigned char *data)
1793 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1795 data[0] = 255 - data[0];
1796 data[1] = 255 - data[1];
1797 data[2] = 255 - data[2];
1801 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1802 static void R_InitBloodTextures (unsigned char *particletexturedata)
1805 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1808 for (i = 0;i < 8;i++)
1810 memset(&data[0][0][0], 255, sizeof(data));
1811 for (k = 0;k < 24;k++)
1812 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1813 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1814 particletextureinvert(&data[0][0][0]);
1815 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1819 for (i = 0;i < 8;i++)
1821 memset(&data[0][0][0], 255, sizeof(data));
1823 for (j = 1;j < 10;j++)
1824 for (k = min(j, m - 1);k < m;k++)
1825 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 192 - j * 8);
1826 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1827 particletextureinvert(&data[0][0][0]);
1828 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1833 //uncomment this to make engine save out particle font to a tga file when run
1834 //#define DUMPPARTICLEFONT
1836 static void R_InitParticleTexture (void)
1838 int x, y, d, i, k, m;
1842 // a note: decals need to modulate (multiply) the background color to
1843 // properly darken it (stain), and they need to be able to alpha fade,
1844 // this is a very difficult challenge because it means fading to white
1845 // (no change to background) rather than black (darkening everything
1846 // behind the whole decal polygon), and to accomplish this the texture is
1847 // inverted (dark red blood on white background becomes brilliant cyan
1848 // and white on black background) so we can alpha fade it to black, then
1849 // we invert it again during the blendfunc to make it work...
1851 #ifndef DUMPPARTICLEFONT
1852 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1853 if (!particlefonttexture)
1856 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1857 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1858 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1861 for (i = 0;i < 8;i++)
1863 memset(&data[0][0][0], 255, sizeof(data));
1866 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1868 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1869 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1871 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1873 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1874 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1876 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1877 d = (noise2[y][x] - 128) * 3 + 192;
1879 d = (int)(d * (1-(dx*dx+dy*dy)));
1880 d = (d * noise1[y][x]) >> 7;
1881 d = bound(0, d, 255);
1882 data[y][x][3] = (unsigned char) d;
1889 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1893 memset(&data[0][0][0], 255, sizeof(data));
1894 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1896 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1897 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1899 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1900 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1901 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1904 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1907 memset(&data[0][0][0], 255, sizeof(data));
1908 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1910 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1911 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1913 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1914 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1915 d = bound(0, d, 255);
1916 data[y][x][3] = (unsigned char) d;
1919 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1922 memset(&data[0][0][0], 255, sizeof(data));
1923 light[0] = 1;light[1] = 1;light[2] = 1;
1924 VectorNormalize(light);
1925 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1927 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1928 // stretch upper half of bubble by +50% and shrink lower half by -50%
1929 // (this gives an elongated teardrop shape)
1931 dy = (dy - 0.5f) * 2.0f;
1933 dy = (dy - 0.5f) / 1.5f;
1934 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1936 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1937 // shrink bubble width to half
1939 data[y][x][3] = shadebubble(dx, dy, light);
1942 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1945 memset(&data[0][0][0], 255, sizeof(data));
1946 light[0] = 1;light[1] = 1;light[2] = 1;
1947 VectorNormalize(light);
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 data[y][x][3] = shadebubble(dx, dy, light);
1957 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1959 // Blood particles and blood decals
1960 R_InitBloodTextures (particletexturedata);
1963 for (i = 0;i < 8;i++)
1965 memset(&data[0][0][0], 255, sizeof(data));
1966 for (k = 0;k < 12;k++)
1967 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1968 for (k = 0;k < 3;k++)
1969 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1970 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1971 particletextureinvert(&data[0][0][0]);
1972 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1975 #ifdef DUMPPARTICLEFONT
1976 Image_WriteTGARGBA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1979 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
1981 Mem_Free(particletexturedata);
1983 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1985 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1986 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1987 particletexture[i].texture = particlefonttexture;
1988 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1989 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1990 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1991 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1994 #ifndef DUMPPARTICLEFONT
1995 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", 0, 0, false, TEXF_ALPHA | TEXF_PRECACHE);
1996 if (!particletexture[tex_beam].texture)
1999 unsigned char noise3[64][64], data2[64][16][4];
2001 fractalnoise(&noise3[0][0], 64, 4);
2003 for (y = 0;y < 64;y++)
2005 dy = (y - 0.5f*64) / (64*0.5f-1);
2006 for (x = 0;x < 16;x++)
2008 dx = (x - 0.5f*16) / (16*0.5f-2);
2009 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2010 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2011 data2[y][x][3] = 255;
2015 #ifdef DUMPPARTICLEFONT
2016 Image_WriteTGARGBA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2018 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_RGBA, TEXF_PRECACHE, NULL);
2020 particletexture[tex_beam].s1 = 0;
2021 particletexture[tex_beam].t1 = 0;
2022 particletexture[tex_beam].s2 = 1;
2023 particletexture[tex_beam].t2 = 1;
2026 static void r_part_start(void)
2028 particletexturepool = R_AllocTexturePool();
2029 R_InitParticleTexture ();
2030 CL_Particles_LoadEffectInfo();
2033 static void r_part_shutdown(void)
2035 R_FreeTexturePool(&particletexturepool);
2038 static void r_part_newmap(void)
2042 #define BATCHSIZE 256
2043 int particle_element3i[BATCHSIZE*6];
2044 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2046 void R_Particles_Init (void)
2049 for (i = 0;i < BATCHSIZE;i++)
2051 particle_element3i[i*6+0] = i*4+0;
2052 particle_element3i[i*6+1] = i*4+1;
2053 particle_element3i[i*6+2] = i*4+2;
2054 particle_element3i[i*6+3] = i*4+0;
2055 particle_element3i[i*6+4] = i*4+2;
2056 particle_element3i[i*6+5] = i*4+3;
2059 Cvar_RegisterVariable(&r_drawparticles);
2060 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2063 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2065 int surfacelistindex;
2066 int batchstart, batchcount;
2067 const particle_t *p;
2069 rtexture_t *texture;
2070 float *v3f, *t2f, *c4f;
2072 R_Mesh_Matrix(&identitymatrix);
2073 R_Mesh_ResetTextureState();
2074 R_Mesh_VertexPointer(particle_vertex3f);
2075 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f);
2076 R_Mesh_ColorPointer(particle_color4f);
2077 GL_DepthMask(false);
2079 GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
2081 // first generate all the vertices at once
2082 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2084 particletexture_t *tex;
2086 float up2[3], v[3], right[3], up[3], fog, ifog, cr, cg, cb, ca, size;
2088 p = cl.particles + surfacelist[surfacelistindex];
2090 blendmode = p->type->blendmode;
2092 cr = p->color[0] * (1.0f / 255.0f) * r_view.colorscale;
2093 cg = p->color[1] * (1.0f / 255.0f) * r_view.colorscale;
2094 cb = p->color[2] * (1.0f / 255.0f) * r_view.colorscale;
2095 ca = p->alpha * (1.0f / 255.0f);
2096 if (blendmode == PBLEND_MOD)
2106 ca /= cl_particles_quality.value;
2107 if (p->type->lighting)
2109 float ambient[3], diffuse[3], diffusenormal[3];
2110 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2111 cr *= (ambient[0] + 0.5 * diffuse[0]);
2112 cg *= (ambient[1] + 0.5 * diffuse[1]);
2113 cb *= (ambient[2] + 0.5 * diffuse[2]);
2115 if (r_refdef.fogenabled)
2117 fog = VERTEXFOGTABLE(VectorDistance(p->org, r_view.origin));
2122 if (blendmode == PBLEND_ALPHA)
2124 cr += r_refdef.fogcolor[0] * fog * r_view.colorscale;
2125 cg += r_refdef.fogcolor[1] * fog * r_view.colorscale;
2126 cb += r_refdef.fogcolor[2] * fog * r_view.colorscale;
2129 c4f[0] = c4f[4] = c4f[8] = c4f[12] = cr;
2130 c4f[1] = c4f[5] = c4f[9] = c4f[13] = cg;
2131 c4f[2] = c4f[6] = c4f[10] = c4f[14] = cb;
2132 c4f[3] = c4f[7] = c4f[11] = c4f[15] = ca;
2134 size = p->size * cl_particles_size.value;
2136 tex = &particletexture[p->texnum];
2137 if (p->type->orientation == PARTICLE_BILLBOARD)
2139 VectorScale(r_view.left, -size, right);
2140 VectorScale(r_view.up, size, up);
2141 v3f[ 0] = org[0] - right[0] - up[0];
2142 v3f[ 1] = org[1] - right[1] - up[1];
2143 v3f[ 2] = org[2] - right[2] - up[2];
2144 v3f[ 3] = org[0] - right[0] + up[0];
2145 v3f[ 4] = org[1] - right[1] + up[1];
2146 v3f[ 5] = org[2] - right[2] + up[2];
2147 v3f[ 6] = org[0] + right[0] + up[0];
2148 v3f[ 7] = org[1] + right[1] + up[1];
2149 v3f[ 8] = org[2] + right[2] + up[2];
2150 v3f[ 9] = org[0] + right[0] - up[0];
2151 v3f[10] = org[1] + right[1] - up[1];
2152 v3f[11] = org[2] + right[2] - up[2];
2153 t2f[0] = tex->s1;t2f[1] = tex->t2;
2154 t2f[2] = tex->s1;t2f[3] = tex->t1;
2155 t2f[4] = tex->s2;t2f[5] = tex->t1;
2156 t2f[6] = tex->s2;t2f[7] = tex->t2;
2158 else if (p->type->orientation == PARTICLE_ORIENTED_DOUBLESIDED)
2161 if (DotProduct(p->vel, r_view.origin) > DotProduct(p->vel, org))
2163 VectorNegate(p->vel, v);
2164 VectorVectors(v, right, up);
2167 VectorVectors(p->vel, right, up);
2168 VectorScale(right, size, right);
2169 VectorScale(up, size, up);
2170 v3f[ 0] = org[0] - right[0] - up[0];
2171 v3f[ 1] = org[1] - right[1] - up[1];
2172 v3f[ 2] = org[2] - right[2] - up[2];
2173 v3f[ 3] = org[0] - right[0] + up[0];
2174 v3f[ 4] = org[1] - right[1] + up[1];
2175 v3f[ 5] = org[2] - right[2] + up[2];
2176 v3f[ 6] = org[0] + right[0] + up[0];
2177 v3f[ 7] = org[1] + right[1] + up[1];
2178 v3f[ 8] = org[2] + right[2] + up[2];
2179 v3f[ 9] = org[0] + right[0] - up[0];
2180 v3f[10] = org[1] + right[1] - up[1];
2181 v3f[11] = org[2] + right[2] - up[2];
2182 t2f[0] = tex->s1;t2f[1] = tex->t2;
2183 t2f[2] = tex->s1;t2f[3] = tex->t1;
2184 t2f[4] = tex->s2;t2f[5] = tex->t1;
2185 t2f[6] = tex->s2;t2f[7] = tex->t2;
2187 else if (p->type->orientation == PARTICLE_SPARK)
2189 VectorMA(org, -0.02, p->vel, v);
2190 VectorMA(org, 0.02, p->vel, up2);
2191 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2192 t2f[0] = tex->s1;t2f[1] = tex->t2;
2193 t2f[2] = tex->s1;t2f[3] = tex->t1;
2194 t2f[4] = tex->s2;t2f[5] = tex->t1;
2195 t2f[6] = tex->s2;t2f[7] = tex->t2;
2197 else if (p->type->orientation == PARTICLE_BEAM)
2199 R_CalcBeam_Vertex3f(v3f, org, p->vel, size);
2200 VectorSubtract(p->vel, org, up);
2201 VectorNormalize(up);
2202 v[0] = DotProduct(org, up) * (1.0f / 64.0f);
2203 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f);
2204 t2f[0] = 1;t2f[1] = v[0];
2205 t2f[2] = 0;t2f[3] = v[0];
2206 t2f[4] = 0;t2f[5] = v[1];
2207 t2f[6] = 1;t2f[7] = v[1];
2211 Con_Printf("R_DrawParticles: unknown particle orientation %i\n", p->type->orientation);
2216 // now render batches of particles based on blendmode and texture
2217 blendmode = PBLEND_ADD;
2218 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2219 texture = particletexture[63].texture;
2220 R_Mesh_TexBind(0, R_GetTexture(texture));
2221 GL_LockArrays(0, numsurfaces*4);
2224 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2226 p = cl.particles + surfacelist[surfacelistindex];
2228 if (blendmode != p->type->blendmode)
2231 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2233 batchstart = surfacelistindex;
2234 blendmode = p->type->blendmode;
2235 if (blendmode == PBLEND_ALPHA)
2236 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2237 else if (blendmode == PBLEND_ADD)
2238 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2239 else //if (blendmode == PBLEND_MOD)
2240 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2242 if (texture != particletexture[p->texnum].texture)
2245 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2247 batchstart = surfacelistindex;
2248 texture = particletexture[p->texnum].texture;
2249 R_Mesh_TexBind(0, R_GetTexture(texture));
2255 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchcount * 2, particle_element3i + batchstart * 6);
2256 GL_LockArrays(0, 0);
2259 void R_DrawParticles (void)
2262 float minparticledist;
2265 // LordHavoc: early out conditions
2266 if ((!cl.num_particles) || (!r_drawparticles.integer))
2269 minparticledist = DotProduct(r_view.origin, r_view.forward) + 4.0f;
2271 // LordHavoc: only render if not too close
2272 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2276 r_refdef.stats.particles++;
2277 if (DotProduct(p->org, r_view.forward) >= minparticledist || p->type->orientation == PARTICLE_BEAM)
2278 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);